From 0d13c63d17fb2aee74ab973c290d845da8034f50 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:10:15 +1000 Subject: [PATCH] remove Card components --- docs/content/docs/fields/relationship.md | 19 +- docs/content/docs/guides/custom-fields.md | 21 - examples/custom-field/1-text-field/views.tsx | 10 - examples/custom-field/2-stars-field/views.tsx | 11 - .../custom-field/3-pair-field-json/views.tsx | 10 - .../3-pair-field-nested/views.tsx | 10 - examples/custom-field/3-pair-field/views.tsx | 10 - .../4-conditional-field/views.tsx | 10 - examples/usecase-blog/schema.ts | 25 +- packages/cloudinary/src/views/index.tsx | 12 - .../admin-ui/id-field-view.tsx | 12 +- packages/core/src/admin-ui/context.tsx | 3 +- .../src/fields/types/bigInt/views/index.tsx | 10 - .../fields/types/calendarDay/views/index.tsx | 10 - .../src/fields/types/checkbox/views/index.tsx | 11 +- .../src/fields/types/decimal/views/index.tsx | 10 - .../src/fields/types/file/views/index.tsx | 12 - .../src/fields/types/float/views/index.tsx | 10 - .../src/fields/types/image/views/index.tsx | 18 +- .../src/fields/types/integer/views/index.tsx | 10 - .../src/fields/types/json/views/index.tsx | 10 - .../fields/types/multiselect/views/index.tsx | 13 - .../src/fields/types/password/views/index.tsx | 10 - .../src/fields/types/relationship/index.ts | 106 +---- .../relationship/views/cards/InlineCreate.tsx | 130 ------ .../relationship/views/cards/InlineEdit.tsx | 147 ------ .../types/relationship/views/cards/index.tsx | 429 ------------------ .../relationship/views/cards/useItemState.tsx | 144 ------ .../fields/types/relationship/views/index.tsx | 147 +----- .../src/fields/types/select/views/index.tsx | 21 +- .../src/fields/types/text/views/index.tsx | 16 +- .../fields/types/timestamp/views/index.tsx | 10 - .../src/fields/types/virtual/views/index.tsx | 10 - packages/core/src/types/admin-meta.ts | 8 - .../fields-document/src/structure-views.tsx | 5 - packages/fields-document/src/views.tsx | 11 - .../label-search-field-validation.test.ts | 96 ---- ...d-item-to-relationship-in-hook-cards-ui.ts | 44 -- tests/sandbox/configs/all-the-things.ts | 19 +- 39 files changed, 31 insertions(+), 1589 deletions(-) delete mode 100644 packages/core/src/fields/types/relationship/views/cards/InlineCreate.tsx delete mode 100644 packages/core/src/fields/types/relationship/views/cards/InlineEdit.tsx delete mode 100644 packages/core/src/fields/types/relationship/views/cards/index.tsx delete mode 100644 packages/core/src/fields/types/relationship/views/cards/useItemState.tsx delete mode 100644 tests/sandbox/configs/7590-add-item-to-relationship-in-hook-cards-ui.ts diff --git a/docs/content/docs/fields/relationship.md b/docs/content/docs/fields/relationship.md index fd85a4a58b9..b82ab605ca9 100644 --- a/docs/content/docs/fields/relationship.md +++ b/docs/content/docs/fields/relationship.md @@ -13,21 +13,14 @@ Read our [relationships guide](../guides/relationships) for details on Keystone - `map`: Changes the column name in the database - `ui` (default: `{ hideCreate: false, displayMode: 'select' }`): Configures the display mode of the field in the Admin UI. - `hideCreate` (default: `false`). If `true`, the "Create related item" button is not shown in the item view. - - `displayMode` (default: `'select'`): Controls the mode used to display the field in the item view. The mode `'select'` displays related items in a select component, while `'cards'` displays the related items in a card layout. Each display mode supports further configuration. + - `displayMode` (default: `'select'`): Controls the mode used to display the field in the item view. The mode `'select'` displays related items in a select component - `ui.displayMode === 'select'` options: - `labelField`: The field path from the related list to use for item labels in the select. Defaults to the `labelField` configured on the related list. -- `searchFields`: The fields used by the UI to search for this item, in context of this relationship field. Defaults to `searchFields` configured on the related list. -- `ui.displayMode === 'cards'` options: - - `cardFields`: A list of field paths from the related list to render in the card component. Defaults to `'id'` and the `labelField` configured on the related list. - - `linkToItem` (default `false`): If `true`, the default card component will render as a link to navigate to the related item. - - `removeMode` (default: `'disconnect'`): Controls whether the `Remove` button is present in the card. If `'disconnect'`, the button will be present. If `'none'`, the button will not be present. - - `inlineCreate` (default: `null`): If not `null`, an object of the form `{ fields: [...] }`, where `fields` is a list of field paths from the related list should be provided. An inline `Create` button will be included in the cards allowing a new related item to be created based on the configured field paths. - - `inlineEdit` (default: `null`): If not `null`, an object of the form `{ fields: [...] }`, where `fields` is a list of field paths from the related list should be provided. An `Edit` button will be included in each card, allowing the configured fields to be edited for each related item. - - `inlineConnect` (default: `false`): If `true`, an inline `Link existing item` button will be present, allowing existing items of the related list to be connected in this field. -Alternatively this can be an object with the properties: - - `labelField`: The field path from the related list to use for item labels in select. Defaults to the `labelField` configured on the related list. - - `searchFields`: The fields used by the UI to search for this item, in context of this relationship field. Defaults to `searchFields` configured on the related list. -- `ui.displayMode === 'count'` only supports `many` relationships + - `searchFields`: The fields used by the UI to search for this item, in context of this relationship field. Defaults to `searchFields` configured on the related list. + +- `ui.displayMode === 'count'` options, which only support `many` relationships: + - `labelField`: The field path from the related list to use for item labels in select. Defaults to the `labelField` configured on the related list. + - `searchFields`: The fields used by the UI to search for this item, in context of this relationship field. Defaults to `searchFields` configured on the related list. ```typescript import { config, list } from '@keystone-6/core'; diff --git a/docs/content/docs/guides/custom-fields.md b/docs/content/docs/guides/custom-fields.md index ec8f629917e..faa18086927 100644 --- a/docs/content/docs/guides/custom-fields.md +++ b/docs/content/docs/guides/custom-fields.md @@ -187,27 +187,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { Cell.supportsLinkTo = true; ``` -### CardValue - -The `CardValue` export is a React component which is shown on the **item view** in relationship fields with `displayMode: 'cards'` when the related item is not being edited. -Note it does not allow modifying the value. - -```tsx -// view.tsx - -import { FieldContainer, FieldLabel } from '@keystone-ui/fields'; -import { CardValueComponent } from '@keystone-6/core/types'; - -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ); -}; -``` - ## Related resources {% related-content %} diff --git a/examples/custom-field/1-text-field/views.tsx b/examples/custom-field/1-text-field/views.tsx index 3d0c9a60322..34e6d602b50 100644 --- a/examples/custom-field/1-text-field/views.tsx +++ b/examples/custom-field/1-text-field/views.tsx @@ -3,7 +3,6 @@ import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keysto import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -38,15 +37,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( config: FieldControllerConfig<{}> ): FieldController => { diff --git a/examples/custom-field/2-stars-field/views.tsx b/examples/custom-field/2-stars-field/views.tsx index a93dac82d5b..a73da92d9dc 100644 --- a/examples/custom-field/2-stars-field/views.tsx +++ b/examples/custom-field/2-stars-field/views.tsx @@ -3,7 +3,6 @@ import { FieldContainer, FieldDescription, FieldLabel } from '@keystone-ui/field import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -30,16 +29,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { // their cell component links to the related item so it can't link to the item that the relationship is on Cell.supportsLinkTo = true -// this is shown on the item page in relationship fields with `displayMode: 'cards'` -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( // the type parameter here needs to align with what is returned from `getAdminMeta` // in the server-side portion of the field type diff --git a/examples/custom-field/3-pair-field-json/views.tsx b/examples/custom-field/3-pair-field-json/views.tsx index 4f464a2fe81..8238cdb9e86 100644 --- a/examples/custom-field/3-pair-field-json/views.tsx +++ b/examples/custom-field/3-pair-field-json/views.tsx @@ -3,7 +3,6 @@ import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keysto import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -60,15 +59,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( config: FieldControllerConfig<{}> ): FieldController< diff --git a/examples/custom-field/3-pair-field-nested/views.tsx b/examples/custom-field/3-pair-field-nested/views.tsx index 4f464a2fe81..8238cdb9e86 100644 --- a/examples/custom-field/3-pair-field-nested/views.tsx +++ b/examples/custom-field/3-pair-field-nested/views.tsx @@ -3,7 +3,6 @@ import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keysto import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -60,15 +59,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( config: FieldControllerConfig<{}> ): FieldController< diff --git a/examples/custom-field/3-pair-field/views.tsx b/examples/custom-field/3-pair-field/views.tsx index 4ad1ec86f02..000297e5220 100644 --- a/examples/custom-field/3-pair-field/views.tsx +++ b/examples/custom-field/3-pair-field/views.tsx @@ -3,7 +3,6 @@ import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keysto import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -38,15 +37,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( config: FieldControllerConfig<{}> ): FieldController => { diff --git a/examples/custom-field/4-conditional-field/views.tsx b/examples/custom-field/4-conditional-field/views.tsx index 0c50f1b5f93..b327658a0dd 100644 --- a/examples/custom-field/4-conditional-field/views.tsx +++ b/examples/custom-field/4-conditional-field/views.tsx @@ -3,7 +3,6 @@ import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keysto import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -52,15 +51,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( config: FieldControllerConfig<{ dependency: { diff --git a/examples/usecase-blog/schema.ts b/examples/usecase-blog/schema.ts index 0f278ce9127..53e1e10d607 100644 --- a/examples/usecase-blog/schema.ts +++ b/examples/usecase-blog/schema.ts @@ -72,16 +72,6 @@ export const lists = { author: relationship({ // we could have used 'Author', but then the relationship would only be 1-way ref: 'Author.posts', - - // we customise how this will look in the AdminUI, for fun - ui: { - displayMode: 'cards', - cardFields: ['name', 'email'], - inlineEdit: { fields: ['name', 'email'] }, - linkToItem: true, - inlineConnect: true, - }, - many: false, // only 1 author for each Post (the default) }), @@ -90,15 +80,12 @@ export const lists = { ref: 'Tag.posts', many: true, // a Post can have many Tags, not just one - // we customise how this will look in the AdminUI, for fun - ui: { - displayMode: 'cards', - cardFields: ['name'], - inlineEdit: { fields: ['name'] }, - linkToItem: true, - inlineConnect: true, - inlineCreate: { fields: ['name'] }, - }, + // TODO: restore after breaking change +// ui: { +// inlineCreate: { +// fields: ['name'] +// } +// } }), }, }), diff --git a/packages/cloudinary/src/views/index.tsx b/packages/cloudinary/src/views/index.tsx index 0f2ee54266f..522d5472386 100644 --- a/packages/cloudinary/src/views/index.tsx +++ b/packages/cloudinary/src/views/index.tsx @@ -3,12 +3,10 @@ import { jsx } from '@keystone-ui/core' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, } from '@keystone-6/core/types' -import { FieldContainer, FieldLabel } from '@keystone-ui/fields' import { validateImage } from './Field' export { Field } from './Field' @@ -35,16 +33,6 @@ export const Cell: CellComponent = ({ item, field }) => { ) } -export const CardValue: CardValueComponent = ({ item, field }) => { - const data = item[field.path] - return ( - - {field.label} - {data && {data.filename}} - - ) -} - type ImageData = { id: string filename: string diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx b/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx index e4bb0c87905..9c1eba2faaf 100644 --- a/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx +++ b/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx @@ -4,9 +4,8 @@ import { TextField } from '@keystar/ui/text-field' import { jsx } from '@keystone-ui/core' -import { FieldContainer, FieldLabel, TextInput } from '@keystone-ui/fields' +import { TextInput } from '@keystone-ui/fields' import type { - CardValueComponent, CellComponent, FieldController, FieldControllerConfig, @@ -24,15 +23,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export function controller ( config: FieldControllerConfig ): FieldController { diff --git a/packages/core/src/admin-ui/context.tsx b/packages/core/src/admin-ui/context.tsx index 906bc71ca9b..745312f7143 100644 --- a/packages/core/src/admin-ui/context.tsx +++ b/packages/core/src/admin-ui/context.tsx @@ -65,8 +65,7 @@ function InternalKeystoneProvider ({ const { push: navigate } = useRouter() const keystarRouter = useMemo(() => ({ navigate }), [navigate]) const adminMeta = useAdminMeta(adminMetaHash, fieldViews) - const { authenticatedItem, visibleLists, createViewFieldModes, refetch } = - useLazyMetadata(lazyMetadataQuery) + const { authenticatedItem, visibleLists, createViewFieldModes, refetch } = useLazyMetadata(lazyMetadataQuery) const reinitContext = async () => { await adminMeta?.refetch?.() await refetch() diff --git a/packages/core/src/fields/types/bigInt/views/index.tsx b/packages/core/src/fields/types/bigInt/views/index.tsx index 9851c81f58a..7434ccf04d5 100644 --- a/packages/core/src/fields/types/bigInt/views/index.tsx +++ b/packages/core/src/fields/types/bigInt/views/index.tsx @@ -5,7 +5,6 @@ import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keystone-ui/fields' import { useState } from 'react' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -131,15 +130,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path] === null ? '' : item[field.path]} - - ) -} - function validate ( state: Value, validation: Validation, diff --git a/packages/core/src/fields/types/calendarDay/views/index.tsx b/packages/core/src/fields/types/calendarDay/views/index.tsx index be5fac3f837..bbdef450b32 100644 --- a/packages/core/src/fields/types/calendarDay/views/index.tsx +++ b/packages/core/src/fields/types/calendarDay/views/index.tsx @@ -5,7 +5,6 @@ import { useState } from 'react' import { jsx, Inline, Stack, Text } from '@keystone-ui/core' import { FieldContainer, FieldLabel, DatePicker, FieldDescription } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -95,15 +94,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {formatOutput(item[field.path])} - - ) -} - function formatOutput (isoDateString: string | null) { if (!isoDateString) { return null diff --git a/packages/core/src/fields/types/checkbox/views/index.tsx b/packages/core/src/fields/types/checkbox/views/index.tsx index ea78c06242f..05da4709d44 100644 --- a/packages/core/src/fields/types/checkbox/views/index.tsx +++ b/packages/core/src/fields/types/checkbox/views/index.tsx @@ -4,8 +4,8 @@ import { FieldLabel } from '@keystar/ui/field' import { VStack } from '@keystar/ui/layout' import { Text } from '@keystar/ui/typography' +import { jsx, useTheme } from '@keystone-ui/core' import type { - CardValueComponent, CellComponent, FieldController, FieldControllerConfig, @@ -35,15 +35,6 @@ export const Cell: CellComponent = ({ item, field }) => { ) } -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path] + ''} - - ) -} - type CheckboxController = FieldController export const controller = ( diff --git a/packages/core/src/fields/types/decimal/views/index.tsx b/packages/core/src/fields/types/decimal/views/index.tsx index 85a29c132da..b484ee23f95 100644 --- a/packages/core/src/fields/types/decimal/views/index.tsx +++ b/packages/core/src/fields/types/decimal/views/index.tsx @@ -6,7 +6,6 @@ import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keysto import { Decimal } from 'decimal.js' import { useState } from 'react' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -82,15 +81,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export type DecimalFieldMeta = { precision: number scale: number diff --git a/packages/core/src/fields/types/file/views/index.tsx b/packages/core/src/fields/types/file/views/index.tsx index c6854fb85ee..5f0a6d6eacb 100644 --- a/packages/core/src/fields/types/file/views/index.tsx +++ b/packages/core/src/fields/types/file/views/index.tsx @@ -2,9 +2,7 @@ /** @jsx jsx */ import { jsx } from '@keystone-ui/core' -import { FieldContainer, FieldLabel } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -32,16 +30,6 @@ export const Cell: CellComponent = ({ item, field }) => { ) } -export const CardValue: CardValueComponent = ({ item, field }) => { - const data = item[field.path] - return ( - - {field.label} - {data && data.filename} - - ) -} - type FileData = { src: string filesize: number diff --git a/packages/core/src/fields/types/float/views/index.tsx b/packages/core/src/fields/types/float/views/index.tsx index 0ab2c8c85f1..d9e8ca4ac00 100644 --- a/packages/core/src/fields/types/float/views/index.tsx +++ b/packages/core/src/fields/types/float/views/index.tsx @@ -5,7 +5,6 @@ import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keystone-ui/fields' import { useState } from 'react' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -162,15 +161,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - export const controller = ( config: FieldControllerConfig<{ validation: Validation, defaultValue: number | null }> ): FieldController & { diff --git a/packages/core/src/fields/types/image/views/index.tsx b/packages/core/src/fields/types/image/views/index.tsx index 1bf5d97061a..cdee1d0a840 100644 --- a/packages/core/src/fields/types/image/views/index.tsx +++ b/packages/core/src/fields/types/image/views/index.tsx @@ -2,14 +2,12 @@ /** @jsx jsx */ import { jsx } from '@keystone-ui/core' -import { FieldContainer, FieldLabel } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, } from '../../../../types' -import { validateImage, ImageWrapper } from './Field' +import { validateImage } from './Field' export { Field } from './Field' @@ -31,20 +29,6 @@ export const Cell: CellComponent = ({ item, field }) => { ) } -export const CardValue: CardValueComponent = ({ item, field }) => { - const data = item[field.path] - return ( - - {field.label} - {data && ( - - {data.filename} - - )} - - ) -} - type ImageData = { src: string height: number diff --git a/packages/core/src/fields/types/integer/views/index.tsx b/packages/core/src/fields/types/integer/views/index.tsx index d543940e066..75d83e06cff 100644 --- a/packages/core/src/fields/types/integer/views/index.tsx +++ b/packages/core/src/fields/types/integer/views/index.tsx @@ -5,7 +5,6 @@ import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keystone-ui/fields' import { useState } from 'react' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -119,15 +118,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path] === null ? '' : item[field.path]} - - ) -} - function validate ( value: Value, validation: Validation, diff --git a/packages/core/src/fields/types/json/views/index.tsx b/packages/core/src/fields/types/json/views/index.tsx index 457afe9ba0a..137eb118b69 100644 --- a/packages/core/src/fields/types/json/views/index.tsx +++ b/packages/core/src/fields/types/json/views/index.tsx @@ -4,7 +4,6 @@ import { jsx, Stack, Text } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel, TextArea } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -62,15 +61,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - type Config = FieldControllerConfig<{ defaultValue: JSONValue }> export const controller = (config: Config): FieldController => { diff --git a/packages/core/src/fields/types/multiselect/views/index.tsx b/packages/core/src/fields/types/multiselect/views/index.tsx index 163fd001efc..62ac18c6c4a 100644 --- a/packages/core/src/fields/types/multiselect/views/index.tsx +++ b/packages/core/src/fields/types/multiselect/views/index.tsx @@ -4,7 +4,6 @@ import { Fragment } from 'react' import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel, MultiSelect } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -43,18 +42,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - const value: readonly string[] | readonly number[] = item[field.path] ?? [] - const label = value.map(value => field.valuesToOptionsWithStringValues[value].label).join(', ') - - return ( - - {field.label} - {label} - - ) -} - export type AdminMultiSelectFieldMeta = { options: readonly { label: string, value: string | number }[] type: 'string' | 'integer' | 'enum' diff --git a/packages/core/src/fields/types/password/views/index.tsx b/packages/core/src/fields/types/password/views/index.tsx index cfde4fed694..ed5e1065183 100644 --- a/packages/core/src/fields/types/password/views/index.tsx +++ b/packages/core/src/fields/types/password/views/index.tsx @@ -13,7 +13,6 @@ import { SegmentedControl } from '@keystone-ui/segmented-control' // @ts-expect-error import dumbPasswords from 'dumb-passwords' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -175,15 +174,6 @@ export const Cell: CellComponent = ({ item, field }) => { return {isSetText(item[field.path]?.isSet)} } -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {isSetText(item[field.path]?.isSet)} - - ) -} - type Validation = { isRequired: boolean rejectCommon: boolean diff --git a/packages/core/src/fields/types/relationship/index.ts b/packages/core/src/fields/types/relationship/index.ts index b37b6a388ac..37b4e313175 100644 --- a/packages/core/src/fields/types/relationship/index.ts +++ b/packages/core/src/fields/types/relationship/index.ts @@ -22,34 +22,6 @@ type SelectDisplayConfig = { } } -type CardsDisplayConfig = { - ui?: { - // Sets the relationship to display as a list of Cards - displayMode: 'cards' - /* The set of fields to render in the default Card component **/ - cardFields: readonly string[] - /** Causes the default Card component to render as a link to navigate to the related item */ - linkToItem?: boolean - /** Determines whether removing a related item in the UI will delete or unlink it */ - removeMode?: 'disconnect' | 'none' // | 'delete' - /** Configures inline create mode for cards (alternative to opening the create modal) */ - inlineCreate?: { fields: readonly string[] } - /** Configures inline edit mode for cards */ - inlineEdit?: { fields: readonly string[] } - /** Configures whether a select to add existing items should be shown or not */ - inlineConnect?: - | boolean - | { - /** - * The path of the field to use from the related list for item labels in the inline connect - * Defaults to the labelField configured on the related list. - */ - labelField: string - searchFields?: string[] - } - } -} - type CountDisplayConfig = { many: true ui?: { @@ -86,7 +58,7 @@ export type RelationshipFieldConfig = hideCreate?: boolean } } & (OneDbConfig | ManyDbConfig) & - (SelectDisplayConfig | CardsDisplayConfig | CountDisplayConfig) + (SelectDisplayConfig | CountDisplayConfig) export const relationship = ({ @@ -107,37 +79,12 @@ export const relationship = views: '@keystone-6/core/fields/types/relationship/views', getAdminMeta: (): Parameters[0]['fieldMeta'] => { const adminMetaRoot = getAdminMetaForRelationshipField() - const localListMeta = adminMetaRoot.listsByKey[listKey] const foreignListMeta = adminMetaRoot.listsByKey[foreignListKey] if (!foreignListMeta) { throw new Error(`The ref [${ref}] on relationship [${listKey}.${fieldKey}] is invalid`) } - if (config.ui?.displayMode === 'cards') { - // we're checking whether the field which will be in the admin meta at the time that getAdminMeta is called. - // in newer versions of keystone, it will be there and it will not be there for older versions of keystone. - // this is so that relationship fields doesn't break in confusing ways - // if people are using a slightly older version of keystone - const currentField = localListMeta.fields.find(x => x.key === fieldKey) - if (currentField) { - const allForeignFields = new Set(foreignListMeta.fields.map(x => x.key)) - for (const [configOption, foreignFields] of [ - ['ui.cardFields', config.ui.cardFields], - ['ui.inlineCreate.fields', config.ui.inlineCreate?.fields ?? []], - ['ui.inlineEdit.fields', config.ui.inlineEdit?.fields ?? []], - ] as const) { - for (const foreignField of foreignFields) { - if (!allForeignFields.has(foreignField)) { - throw new Error( - `The ${configOption} option on the relationship field at ${listKey}.${fieldKey} includes the "${foreignField}" field but that field does not exist on the "${foreignListKey}" list` - ) - } - } - } - } - } - const hideCreate = config.ui?.hideCreate ?? false const refLabelField: typeof foreignFieldKey = foreignListMeta.labelField const refSearchFields: (typeof foreignFieldKey)[] = foreignListMeta.fields @@ -156,57 +103,6 @@ export const relationship = } } - if (config.ui?.displayMode === 'cards') { - // prefer the local definition to the foreign list, if provided - const inlineConnectConfig = - typeof config.ui.inlineConnect === 'object' - ? { - refLabelField: config.ui.inlineConnect.labelField ?? refLabelField, - refSearchFields: config.ui.inlineConnect?.searchFields ?? refSearchFields, - } - : { - refLabelField, - refSearchFields, - } - - if (!(inlineConnectConfig.refLabelField in foreignListMeta.fieldsByKey)) { - throw new Error( - `The ui.inlineConnect.labelField option for field '${listKey}.${fieldKey}' uses '${inlineConnectConfig.refLabelField}' but that field doesn't exist.` - ) - } - - for (const searchFieldKey of inlineConnectConfig.refSearchFields) { - if (!(searchFieldKey in foreignListMeta.fieldsByKey)) { - throw new Error( - `The ui.inlineConnect.searchFields option for relationship field '${listKey}.${fieldKey}' includes '${searchFieldKey}' but that field doesn't exist.` - ) - } - - const field = foreignListMeta.fieldsByKey[searchFieldKey] - if (field.search) continue - - throw new Error( - `The ui.searchFields option for field '${listKey}.${fieldKey}' includes '${searchFieldKey}' but that field doesn't have a contains filter that accepts a GraphQL String` - ) - } - - return { - refFieldKey: foreignFieldKey, - refListKey: foreignListKey, - many, - hideCreate, - displayMode: 'cards', - cardFields: config.ui.cardFields, - linkToItem: config.ui.linkToItem ?? false, - removeMode: config.ui.removeMode ?? 'disconnect', - inlineCreate: config.ui.inlineCreate ?? null, - inlineEdit: config.ui.inlineEdit ?? null, - inlineConnect: config.ui.inlineConnect ? true : false, - - ...inlineConnectConfig, - } - } - // prefer the local definition to the foreign list, if provided const specificRefLabelField = config.ui?.labelField || refLabelField const specificRefSearchFields = config.ui?.searchFields || refSearchFields diff --git a/packages/core/src/fields/types/relationship/views/cards/InlineCreate.tsx b/packages/core/src/fields/types/relationship/views/cards/InlineCreate.tsx deleted file mode 100644 index c38a889a23e..00000000000 --- a/packages/core/src/fields/types/relationship/views/cards/InlineCreate.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/** @jsxRuntime classic */ -/** @jsx jsx */ - -import { useState } from 'react' -import { jsx, Stack } from '@keystone-ui/core' -import isDeepEqual from 'fast-deep-equal' -import { useToasts } from '@keystone-ui/toast' -import { Button } from '@keystone-ui/button' -import { type ListMeta } from '../../../../../types' -import { - type ItemData, - makeDataGetter, - type DataGetter, - type Value, - useInvalidFields, - serializeValueToObjByFieldKey, - Fields, -} from '../../../../../admin-ui/utils' -import { gql, useMutation } from '../../../../../admin-ui/apollo' -import { GraphQLErrorNotice } from '../../../../../admin-ui/components' -import { useFieldsObj } from './useItemState' - -export function InlineCreate ({ - list, - onCancel, - onCreate, - fields: fieldPaths, - selectedFields, -}: { - list: ListMeta - selectedFields: string - fields: readonly string[] - onCancel: () => void - onCreate: (itemGetter: DataGetter) => void -}) { - const toasts = useToasts() - const fields = useFieldsObj(list, fieldPaths) - - const [createItem, { loading, error }] = useMutation( - gql`mutation($data: ${list.gqlNames.createInputName}!) { - item: ${list.gqlNames.createMutationName}(data: $data) { - ${selectedFields} - } - }` - ) - - const [value, setValue] = useState(() => { - const value: Value = {} - Object.keys(fields).forEach(fieldPath => { - value[fieldPath] = { kind: 'value', value: fields[fieldPath].controller.defaultValue } - }) - return value - }) - - const invalidFields = useInvalidFields(fields, value) - const [forceValidation, setForceValidation] = useState(false) - - const onSubmit = () => { - const newForceValidation = invalidFields.size !== 0 - setForceValidation(newForceValidation) - - if (newForceValidation) return - const data: Record = {} - const allSerializedValues = serializeValueToObjByFieldKey(fields, value) - Object.keys(allSerializedValues).forEach(fieldPath => { - const { controller } = fields[fieldPath] - const serialized = allSerializedValues[fieldPath] - if (!isDeepEqual(serialized, controller.serialize(controller.defaultValue))) { - Object.assign(data, serialized) - } - }) - - createItem({ - variables: { - data, - }, - }) - .then(({ data, errors }) => { - // we're checking for path.length === 1 because errors with a path larger than 1 will be field level errors - // which are handled seperately and do not indicate a failure to update the item - const error = errors?.find(x => x.path?.length === 1) - if (error) { - toasts.addToast({ - title: 'Failed to create item', - tone: 'negative', - message: error.message, - }) - } else { - toasts.addToast({ - title: data.item[list.labelField] || data.item.id, - tone: 'positive', - message: 'Saved successfully', - }) - onCreate(makeDataGetter(data, errors).get('item')) - } - }) - .catch(err => { - toasts.addToast({ - title: 'Failed to update item', - tone: 'negative', - message: err.message, - }) - }) - } - - return ( -
- - {error && ( - - )} - - - - - - -
- ) -} diff --git a/packages/core/src/fields/types/relationship/views/cards/InlineEdit.tsx b/packages/core/src/fields/types/relationship/views/cards/InlineEdit.tsx deleted file mode 100644 index 4857dd930dd..00000000000 --- a/packages/core/src/fields/types/relationship/views/cards/InlineEdit.tsx +++ /dev/null @@ -1,147 +0,0 @@ -/** @jsxRuntime classic */ -/** @jsx jsx */ - -import { Button } from '@keystone-ui/button' -import { jsx, Stack } from '@keystone-ui/core' -import { useToasts } from '@keystone-ui/toast' -import { useCallback, useState } from 'react' -import { type ListMeta } from '../../../../../types' -import { - deserializeValue, - type ItemData, - useInvalidFields, - Fields, - useChangedFieldsAndDataForUpdate, - makeDataGetter, - type DataGetter, -} from '../../../../../admin-ui/utils' -import { gql, useMutation } from '../../../../../admin-ui/apollo' -import { GraphQLErrorNotice } from '../../../../../admin-ui/components' -import { useFieldsObj } from './useItemState' - -export function InlineEdit ({ - fields, - list, - selectedFields, - itemGetter, - onCancel, - onSave, -}: { - fields: readonly string[] - list: ListMeta - selectedFields: string - itemGetter: DataGetter - onCancel: () => void - onSave: (newItemGetter: DataGetter) => void -}) { - const fieldsObj = useFieldsObj(list, fields) - - const [update, { loading, error }] = useMutation( - gql`mutation ($data: ${list.gqlNames.updateInputName}!, $id: ID!) { - item: ${list.gqlNames.updateMutationName}(where: { id: $id }, data: $data) { - ${selectedFields} - } - }`, - { errorPolicy: 'all' } - ) - - const [state, setValue] = useState(() => { - const value = deserializeValue(fieldsObj, itemGetter) - return { value, item: itemGetter.data } - }) - - if (state.item !== itemGetter.data && itemGetter.errors?.every(x => x.path?.length !== 1)) { - const value = deserializeValue(fieldsObj, itemGetter) - setValue({ value, item: itemGetter.data }) - } - - const { changedFields, dataForUpdate } = useChangedFieldsAndDataForUpdate( - fieldsObj, - itemGetter, - state.value - ) - - const invalidFields = useInvalidFields(fieldsObj, state.value) - - const [forceValidation, setForceValidation] = useState(false) - const toasts = useToasts() - - return ( -
{ - event.preventDefault() - if (changedFields.size === 0) { - onCancel() - return - } - const newForceValidation = invalidFields.size !== 0 - setForceValidation(newForceValidation) - if (newForceValidation) return - - update({ - variables: { - data: dataForUpdate, - id: itemGetter.get('id').data, - }, - }) - .then(({ data, errors }) => { - // we're checking for path.length === 1 because errors with a path larger than 1 will be field level errors - // which are handled seperately and do not indicate a failure to update the item - const error = errors?.find(x => x.path?.length === 1) - if (error) { - toasts.addToast({ - title: 'Failed to update item', - tone: 'negative', - message: error.message, - }) - } else { - toasts.addToast({ - title: data.item[list.labelField] || data.item.id, - tone: 'positive', - message: 'Saved successfully', - }) - onSave(makeDataGetter(data, errors).get('item')) - } - }) - .catch(err => { - toasts.addToast({ - title: 'Failed to update item', - tone: 'negative', - message: err.message, - }) - }) - }} - > - - {error && ( - x.path?.length === 1)} - /> - )} - { - setValue(state => ({ item: state.item, value: value(state.value) })) - }, - [setValue] - )} - value={state.value} - /> - - - - - -
- ) -} diff --git a/packages/core/src/fields/types/relationship/views/cards/index.tsx b/packages/core/src/fields/types/relationship/views/cards/index.tsx deleted file mode 100644 index eda8be6ebc2..00000000000 --- a/packages/core/src/fields/types/relationship/views/cards/index.tsx +++ /dev/null @@ -1,429 +0,0 @@ -/** @jsxRuntime classic */ -/** @jsx jsx */ - -import Link from 'next/link' -import { type ReactNode, useEffect, useRef, useState } from 'react' - -import { - Box, - type BoxProps, - Stack, - Text, - jsx, - useTheme, - forwardRefWithAs, - VisuallyHidden, -} from '@keystone-ui/core' -import { FieldContainer, FieldLabel } from '@keystone-ui/fields' -import { Button } from '@keystone-ui/button' -import { Tooltip } from '@keystone-ui/tooltip' -import { LoadingDots } from '@keystone-ui/loading' - -import { type FieldProps, type ListMeta } from '../../../../../types' -import { - getRootGraphQLFieldsFromFieldController, - makeDataGetter, -} from '../../../../../admin-ui/utils' -import { gql, useApolloClient } from '../../../../../admin-ui/apollo' -import { type controller } from '../index' -import { RelationshipSelect } from '../RelationshipSelect' -import { useItemState } from './useItemState' -import { InlineEdit } from './InlineEdit' -import { InlineCreate } from './InlineCreate' - -type CardContainerProps = { - children: ReactNode - mode: 'view' | 'create' | 'edit' -} & BoxProps -const CardContainer = forwardRefWithAs(({ mode = 'view', ...props }: CardContainerProps, ref) => { - const { tones } = useTheme() - - const tone = tones[mode === 'edit' ? 'active' : mode === 'create' ? 'positive' : 'passive'] - - return ( - - ) -}) - -export function Cards ({ - localList, - field, - foreignList, - id, - value, - onChange, - forceValidation, -}: { - foreignList: ListMeta - localList: ListMeta - id: string | null - value: { kind: 'cards-view' } -} & FieldProps) { - const { displayOptions } = value - let selectedFields = [ - ...new Set([...displayOptions.cardFields, ...(displayOptions.inlineEdit?.fields || [])]), - ] - .map(fieldPath => { - return foreignList.fields[fieldPath].controller.graphqlSelection - }) - .join('\n') - if (!displayOptions.cardFields.includes('id')) { - selectedFields += '\nid' - } - if ( - !displayOptions.cardFields.includes(foreignList.labelField) && - foreignList.labelField !== 'id' - ) { - selectedFields += `\n${foreignList.labelField}` - } - - const { - items, - setItems, - state: itemsState, - } = useItemState({ - selectedFields, - localList, - id, - field, - }) - - const client = useApolloClient() - - const [isLoadingLazyItems, setIsLoadingLazyItems] = useState(false) - const [showConnectItems, setShowConnectItems] = useState(false) - const [hideConnectItemsLabel, setHideConnectItemsLabel] = useState<'Cancel' | 'Done'>('Cancel') - const editRef = useRef(null) - - const isMountedRef = useRef(false) - useEffect(() => { - isMountedRef.current = true - return () => { - isMountedRef.current = false - } - }) - - useEffect(() => { - if (value.itemsBeingEdited) { - editRef?.current?.focus() - } - }, [value]) - - if (itemsState.kind === 'loading') { - return ( -
- -
- ) - } - if (itemsState.kind === 'error') { - return {itemsState.message} - } - - const currentIdsArrayWithFetchedItems = [...value.currentIds] - .map(id => ({ itemGetter: items[id], id })) - .filter(x => x.itemGetter) - - return ( - - {currentIdsArrayWithFetchedItems.length !== 0 && ( - - {currentIdsArrayWithFetchedItems.map(({ id, itemGetter }, index) => { - const isEditMode = !!(onChange !== undefined) && value.itemsBeingEdited.has(id) - return ( - - {`${field.label} ${index + 1} ${ - isEditMode ? 'edit' : 'view' - } mode`} - {isEditMode ? ( - { - setItems({ - ...items, - [id]: newItemGetter, - }) - const itemsBeingEdited = new Set(value.itemsBeingEdited) - itemsBeingEdited.delete(id) - onChange!({ - ...value, - itemsBeingEdited, - }) - }} - selectedFields={selectedFields} - itemGetter={itemGetter} - onCancel={() => { - const itemsBeingEdited = new Set(value.itemsBeingEdited) - itemsBeingEdited.delete(id) - onChange!({ - ...value, - itemsBeingEdited, - }) - }} - /> - ) : ( - - {displayOptions.cardFields.map(fieldPath => { - const field = foreignList.fields[fieldPath] - const itemForField: Record = {} - for (const graphqlField of getRootGraphQLFieldsFromFieldController( - field.controller - )) { - const fieldGetter = itemGetter.get(graphqlField) - if (fieldGetter.errors) { - const errorMessage = fieldGetter.errors[0].message - return ( - - {field.label} - {errorMessage} - - ) - } - itemForField[graphqlField] = fieldGetter.data - } - return ( - - ) - })} - - {displayOptions.inlineEdit && onChange !== undefined && ( - - )} - {displayOptions.removeMode === 'disconnect' && onChange !== undefined && ( - - {props => ( - - )} - - )} - {displayOptions.linkToItem && ( - - )} - - - )} - - ) - })} - - )} - {onChange === undefined ? null : displayOptions.inlineConnect && showConnectItems ? ( - - - { - if (!value.currentIds.has(item.id)) { - itemsToFetchAndConnect.push(item.id) - } - }) - if (itemsToFetchAndConnect.length) { - try { - const { data, errors } = await client.query({ - query: gql`query ($ids: [ID!]!) { - items: ${foreignList.gqlNames.listQueryName}(where: { id: { in: $ids }}) { - ${selectedFields} - } - }`, - variables: { ids: itemsToFetchAndConnect }, - }) - if (isMountedRef.current) { - const dataGetters = makeDataGetter(data, errors) - const itemsDataGetter = dataGetters.get('items') - let newItems = { ...items } - let newCurrentIds = field.many - ? new Set(value.currentIds) - : new Set() - if (Array.isArray(itemsDataGetter.data)) { - itemsDataGetter.data.forEach((item, i) => { - if (item?.id != null) { - newCurrentIds.add(item.id) - newItems[item.id] = itemsDataGetter.get(i) - } - }) - } - if (newCurrentIds.size) { - setItems(newItems) - onChange({ - ...value, - currentIds: newCurrentIds, - }) - setHideConnectItemsLabel('Done') - } - } - } finally { - if (isMountedRef.current) { - setIsLoadingLazyItems(false) - } - } - } - }, - value: (() => { - let options: { label: string, id: string }[] = [] - Object.keys(items).forEach(id => { - if (value.currentIds.has(id)) { - options.push({ id, label: id }) - } - }) - return options - })(), - }} - /> - - - - ) : value.itemBeingCreated ? ( - - { - onChange({ ...value, itemBeingCreated: false }) - }} - onCreate={itemGetter => { - const id = itemGetter.data.id - setItems({ ...items, [id]: itemGetter }) - onChange({ - ...value, - itemBeingCreated: false, - currentIds: field.many ? new Set([...value.currentIds, id]) : new Set([id]), - }) - }} - /> - - ) : displayOptions.inlineCreate || displayOptions.inlineConnect ? ( - - - {displayOptions.inlineCreate && ( - - )} - {displayOptions.inlineConnect && ( - - )} - - - ) : null} - {/* TODO: this may not be visible to the user when they invoke the save action. Maybe scroll to it? */} - {forceValidation && ( - - You must finish creating and editing any related {foreignList.label.toLowerCase()} before - saving the {localList.singular.toLowerCase()} - - )} - - ) -} diff --git a/packages/core/src/fields/types/relationship/views/cards/useItemState.tsx b/packages/core/src/fields/types/relationship/views/cards/useItemState.tsx deleted file mode 100644 index 27a1dc9e1d6..00000000000 --- a/packages/core/src/fields/types/relationship/views/cards/useItemState.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { useCallback, useMemo, useState } from 'react' -import { type FieldMeta, type ListMeta } from '../../../../../types' -import { type DataGetter, makeDataGetter } from '../../../../../admin-ui/utils' -import { gql, useQuery } from '../../../../../admin-ui/apollo' -import { type controller } from '../index' - -type ItemsState = - | { kind: 'loading' } - | { kind: 'error', message: string } - | { kind: 'loaded' } - -type Items = Record> - -export function useItemState ({ - selectedFields, - localList, - id, - field, -}: { - selectedFields: string - localList: ListMeta - field: ReturnType - id: string | null -}) { - const { data, error, loading } = useQuery( - gql`query($id: ID!) { - item: ${localList.gqlNames.itemQueryName}(where: {id: $id}) { - id - relationship: ${field.path} { - ${selectedFields} - } - } -}`, - { variables: { id }, errorPolicy: 'all', skip: id === null } - ) - const { itemsArrFromData, relationshipGetter } = useMemo(() => { - const dataGetter = makeDataGetter(data, error?.graphQLErrors) - const relationshipGetter = dataGetter.get('item').get('relationship') - const isMany = Array.isArray(relationshipGetter.data) - const itemsArrFromData: DataGetter<{ id: string, [key: string]: any }>[] = ( - isMany - ? relationshipGetter.data.map((_: any, i: number) => relationshipGetter.get(i)) - : [relationshipGetter] - ).filter((x: DataGetter) => x.data?.id != null) - return { relationshipGetter, itemsArrFromData } - }, [data, error]) - let [{ items, itemsArrFromData: itemsArrFromDataState }, setItemsState] = useState<{ - itemsArrFromData: DataGetter[] - items: Record< - string, - { - current: DataGetter<{ id: string, [key: string]: any }> - fromInitialQuery: DataGetter<{ id: string, [key: string]: any }> | undefined - } - > - }>({ itemsArrFromData: [], items: {} }) - - if (itemsArrFromDataState !== itemsArrFromData) { - let newItems: Record< - string, - { - current: DataGetter<{ id: string, [key: string]: any }> - fromInitialQuery: DataGetter<{ id: string, [key: string]: any }> | undefined - } - > = {} - - itemsArrFromData.forEach(item => { - const initialItemInState = items[item.data.id]?.fromInitialQuery - if ( - ((items[item.data.id] && initialItemInState) || !items[item.data.id]) && - (!initialItemInState || - item.data !== initialItemInState.data || - item.errors?.length !== initialItemInState.errors?.length || - (item.errors || []).some((err, i) => err !== initialItemInState.errors?.[i])) - ) { - newItems[item.data.id] = { current: item, fromInitialQuery: item } - } else { - newItems[item.data.id] = items[item.data.id] - } - }) - items = newItems - setItemsState({ - items: newItems, - itemsArrFromData, - }) - } - - return { - items: useMemo(() => { - const itemsToReturn: Items = {} - Object.keys(items).forEach(id => { - itemsToReturn[id] = items[id].current - }) - return itemsToReturn - }, [items]), - setItems: useCallback( - (items: Items) => { - setItemsState(state => { - let itemsForState: (typeof state)['items'] = {} - Object.keys(items).forEach(id => { - if (items[id] === state.items[id]?.current) { - itemsForState[id] = state.items[id] - } else { - itemsForState[id] = { - current: items[id], - fromInitialQuery: state.items[id]?.fromInitialQuery, - } - } - }) - return { - itemsArrFromData: state.itemsArrFromData, - items: itemsForState, - } - }) - }, - [setItemsState] - ), - state: ((): ItemsState => { - if (id === null) { - return { kind: 'loaded' } - } - if (loading) { - return { kind: 'loading' } - } - if (error?.networkError) { - return { kind: 'error', message: error.networkError.message } - } - if (field.many && !relationshipGetter.data) { - return { kind: 'error', message: relationshipGetter.errors?.[0].message || '' } - } - return { kind: 'loaded' } - })(), - } -} - -export function useFieldsObj (list: ListMeta, fields: readonly string[] | undefined) { - return useMemo(() => { - const editFields: Record = {} - fields?.forEach(fieldPath => { - editFields[fieldPath] = list.fields[fieldPath] - }) - return editFields - }, [fields, list.fields]) -} diff --git a/packages/core/src/fields/types/relationship/views/index.tsx b/packages/core/src/fields/types/relationship/views/index.tsx index bd41245c728..efdd4a19faf 100644 --- a/packages/core/src/fields/types/relationship/views/index.tsx +++ b/packages/core/src/fields/types/relationship/views/index.tsx @@ -9,7 +9,6 @@ import { jsx, Stack, useTheme } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel, FieldLegend } from '@keystone-ui/fields' import { DrawerController } from '@keystone-ui/modals' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -20,7 +19,6 @@ import { useKeystone, useList } from '../../../../admin-ui/context' import { gql, useQuery } from '../../../../admin-ui/apollo' import { CellContainer, CreateItemDrawer } from '../../../../admin-ui/components' -import { Cards } from './cards' import { RelationshipSelect } from './RelationshipSelect' function LinkToRelatedItems ({ @@ -84,25 +82,6 @@ export const Field = ({ const localList = useList(field.listKey) const [isDrawerOpen, setIsDrawerOpen] = useState(false) - if (value.kind === 'cards-view') { - return ( - - {field.label} - {field.description} - - - ) - } - if (value.kind === 'count') { return ( @@ -283,26 +262,6 @@ export const Cell: CellComponent = ({ field, item }) => { ) } -export const CardValue: CardValueComponent = ({ field, item }) => { - const list = useList(field.refListKey) - const data = item[field.path] - return ( - - {field.label} - {(Array.isArray(data) ? data : [data]) - .filter(item => item) - .map((item, index) => ( - - {!!index ? ', ' : ''} - - {item.label || item.id} - - - ))} - - ) -} - type SingleRelationshipValue = { kind: 'one' id: null | string @@ -315,34 +274,17 @@ type ManyRelationshipValue = { initialValue: { label: string, id: string }[] value: { label: string, id: string }[] } -type CardsRelationshipValue = { - kind: 'cards-view' - id: null | string - itemsBeingEdited: ReadonlySet - itemBeingCreated: boolean - initialIds: ReadonlySet - currentIds: ReadonlySet - displayOptions: CardsDisplayModeOptions -} type CountRelationshipValue = { kind: 'count' id: null | string count: number } -type CardsDisplayModeOptions = { - cardFields: readonly string[] - linkToItem: boolean - removeMode: 'disconnect' | 'none' - inlineCreate: { fields: readonly string[] } | null - inlineEdit: { fields: readonly string[] } | null - inlineConnect: boolean -} type RelationshipController = FieldController< - ManyRelationshipValue | SingleRelationshipValue | CardsRelationshipValue | CountRelationshipValue, + ManyRelationshipValue | SingleRelationshipValue | CountRelationshipValue, string > & { - display: 'count' | 'cards-or-select' + display: 'select' | 'count' listKey: string refListKey: string refFieldKey?: string @@ -365,33 +307,12 @@ export function controller ( | { displayMode: 'select' } - | { - displayMode: 'cards' - cardFields: readonly string[] - linkToItem: boolean - removeMode: 'disconnect' | 'none' - inlineCreate: { fields: readonly string[] } | null - inlineEdit: { fields: readonly string[] } | null - inlineConnect: boolean - } | { displayMode: 'count' } ) > ): RelationshipController { - const cardsDisplayOptions = - config.fieldMeta.displayMode === 'cards' - ? { - cardFields: config.fieldMeta.cardFields, - inlineCreate: config.fieldMeta.inlineCreate, - inlineEdit: config.fieldMeta.inlineEdit, - linkToItem: config.fieldMeta.linkToItem, - removeMode: config.fieldMeta.removeMode, - inlineConnect: config.fieldMeta.inlineConnect, - } - : undefined - const refLabelField = config.fieldMeta.refLabelField const refSearchFields = config.fieldMeta.refSearchFields @@ -402,7 +323,7 @@ export function controller ( path: config.path, label: config.label, description: config.description, - display: config.fieldMeta.displayMode === 'count' ? 'count' : 'cards-or-select', + display: config.fieldMeta.displayMode, refLabelField, refSearchFields, refListKey: config.fieldMeta.refListKey, @@ -419,17 +340,7 @@ export function controller ( // because our other UIs don't handle relationships with a large number of items well // but that's not a problem here since we're creating a new item so we might as well them a better UI defaultValue: - cardsDisplayOptions !== undefined - ? { - kind: 'cards-view', - currentIds: new Set(), - id: null, - initialIds: new Set(), - itemBeingCreated: false, - itemsBeingEdited: new Set(), - displayOptions: cardsDisplayOptions, - } - : config.fieldMeta.many + config.fieldMeta.many ? { id: null, kind: 'many', @@ -441,25 +352,6 @@ export function controller ( if (config.fieldMeta.displayMode === 'count') { return { id: data.id, kind: 'count', count: data[`${config.path}Count`] ?? 0 } } - if (cardsDisplayOptions !== undefined) { - const initialIds = new Set( - (Array.isArray(data[config.path]) - ? data[config.path] - : data[config.path] - ? [data[config.path]] - : [] - ).map((x: any) => x.id) - ) - return { - kind: 'cards-view', - id: data.id, - itemsBeingEdited: new Set(), - itemBeingCreated: false, - initialIds, - currentIds: initialIds, - displayOptions: cardsDisplayOptions, - } - } if (config.fieldMeta.many) { let value = (data[config.path] || []).map((x: any) => ({ id: x.id, @@ -561,10 +453,7 @@ export function controller ( }, }, validate (value) { - return ( - value.kind !== 'cards-view' || - (value.itemsBeingEdited.size === 0 && !value.itemBeingCreated) - ) + return true }, serialize: state => { if (state.kind === 'many') { @@ -601,32 +490,6 @@ export function controller ( }, } } - } else if (state.kind === 'cards-view') { - let disconnect = [...state.initialIds] - .filter(id => !state.currentIds.has(id)) - .map(id => ({ id })) - let connect = [...state.currentIds] - .filter(id => !state.initialIds.has(id)) - .map(id => ({ id })) - - if (config.fieldMeta.many) { - if (disconnect.length || connect.length) { - return { - [config.path]: { - connect: connect.length ? connect : undefined, - disconnect: disconnect.length ? disconnect : undefined, - }, - } - } - } else if (connect.length) { - return { - [config.path]: { - connect: connect[0], - }, - } - } else if (disconnect.length) { - return { [config.path]: { disconnect: true } } - } } return {} }, diff --git a/packages/core/src/fields/types/select/views/index.tsx b/packages/core/src/fields/types/select/views/index.tsx index c300ccf4797..fe7e97bd4e7 100644 --- a/packages/core/src/fields/types/select/views/index.tsx +++ b/packages/core/src/fields/types/select/views/index.tsx @@ -13,7 +13,6 @@ import { NullableFieldWrapper } from '../../../../admin-ui/components' import { SegmentedControl } from './SegmentedControl' import type { - CardValueComponent, CellComponent, FieldController, FieldControllerConfig, @@ -32,7 +31,7 @@ export const Field = (props: FieldProps) => { return item.label.length > acc ? item.label.length : acc }, 0) }, [field.options]) - + const selectedKey = value.value?.value || preNullValue?.value || null const isNullable = !field.isRequired const isNull = isNullable && value.value?.value == null @@ -138,7 +137,7 @@ export const Field = (props: FieldProps) => { )} ) - } + } })() @@ -163,18 +162,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - let value = item[field.path] + '' - const valueLabel = field.options.find(x => x.value === value)?.label - - return ( - - {field.label} - {valueLabel} - - ) -} - export type AdminSelectFieldMeta = { options: readonly { label: string, value: string | number }[] type: 'string' | 'integer' | 'enum' @@ -323,7 +310,7 @@ export const controller = ( if (value.length === 0) { return type === 'not_matches' ? `is set` : `is not set` } - + const labels = value.map(i => i.label) const prefix = type === 'not_matches' ? `is not` : `is` @@ -331,7 +318,7 @@ export const controller = ( return `${prefix} ${labels[0]}` } if (value.length === 2) { - return `${prefix} ${listFormatter.format(labels)}` + return `${prefix} ${listFormatter.format(labels)}` } return `${prefix} ${listFormatter.format([labels[0], `${value.length - 1} more`])}` diff --git a/packages/core/src/fields/types/text/views/index.tsx b/packages/core/src/fields/types/text/views/index.tsx index 59b4fac4880..e16499568a8 100644 --- a/packages/core/src/fields/types/text/views/index.tsx +++ b/packages/core/src/fields/types/text/views/index.tsx @@ -6,7 +6,6 @@ import { TextArea, TextField } from '@keystar/ui/text-field' import { Text } from '@keystar/ui/typography' import type { - CardValueComponent, CellComponent, FieldController, FieldControllerConfig, @@ -20,7 +19,7 @@ export function Field (props: FieldProps) { const [shouldShowErrors, setShouldShowErrors] = useState(false) const validationMessages = validate(value, field.validation, field.label) - + const isReadOnly = onChange == null const isNull = value.inner.kind === 'null' const isTextArea = field.displayMode === 'textarea' @@ -35,11 +34,11 @@ export function Field (props: FieldProps) { isNull={isNull} onChange={() => { if (!onChange) return - + const inner = value.inner.kind === 'value' ? { kind: 'null', prev: value.inner.value } as const : { kind: 'value', value: value.inner.prev } as const - + onChange({ ...value, inner }) }} > @@ -87,15 +86,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {item[field.path]} - - ) -} - type Config = FieldControllerConfig type Validation = { diff --git a/packages/core/src/fields/types/timestamp/views/index.tsx b/packages/core/src/fields/types/timestamp/views/index.tsx index a08cf689091..edbe80c3538 100644 --- a/packages/core/src/fields/types/timestamp/views/index.tsx +++ b/packages/core/src/fields/types/timestamp/views/index.tsx @@ -11,7 +11,6 @@ import { FieldDescription, } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -213,15 +212,6 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - {formatOutput(item[field.path])} - - ) -} - export type TimestampFieldMeta = { defaultValue: string | { kind: 'now' } | null updatedAt: boolean diff --git a/packages/core/src/fields/types/virtual/views/index.tsx b/packages/core/src/fields/types/virtual/views/index.tsx index bb60f368d2c..f145cd370b6 100644 --- a/packages/core/src/fields/types/virtual/views/index.tsx +++ b/packages/core/src/fields/types/virtual/views/index.tsx @@ -4,7 +4,6 @@ import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -26,15 +25,6 @@ export const Cell: CellComponent = ({ item, field }) => { return } -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - - - ) -} - const createViewValue = Symbol('create view virtual field value') export const controller = ( diff --git a/packages/core/src/types/admin-meta.ts b/packages/core/src/types/admin-meta.ts index db7df026897..f06fc9d8eaf 100644 --- a/packages/core/src/types/admin-meta.ts +++ b/packages/core/src/types/admin-meta.ts @@ -146,7 +146,6 @@ export type FieldViews = Record< { Field: (props: FieldProps) => ReactElement | null Cell: CellComponent - CardValue: CardValueComponent controller: (args: FieldControllerConfig) => FieldController allowedExportsOnCustomViews?: string[] } @@ -166,10 +165,3 @@ export type CellComponent< supportsLinkTo?: boolean } - -export type CardValueComponent< - FieldControllerFn extends (...args: any) => FieldController = () => FieldController< - any, - any - > -> = (props: { item: Record, field: ReturnType }) => ReactElement diff --git a/packages/fields-document/src/structure-views.tsx b/packages/fields-document/src/structure-views.tsx index a4f49775890..390d083dca5 100644 --- a/packages/fields-document/src/structure-views.tsx +++ b/packages/fields-document/src/structure-views.tsx @@ -5,7 +5,6 @@ import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldLabel } from '@keystone-ui/fields' import { - type CardValueComponent, type CellComponent, type FieldController, type FieldControllerConfig, @@ -54,10 +53,6 @@ export const Cell: CellComponent = () => { return null } -export const CardValue: CardValueComponent = () => { - return null as any -} - export const allowedExportsOnCustomViews = ['schema'] export function controller ( diff --git a/packages/fields-document/src/views.tsx b/packages/fields-document/src/views.tsx index 84323a5e775..28920cf86f4 100644 --- a/packages/fields-document/src/views.tsx +++ b/packages/fields-document/src/views.tsx @@ -5,10 +5,8 @@ import { jsx } from '@keystone-ui/core' import { FieldContainer, FieldDescription, FieldLabel } from '@keystone-ui/fields' import { Node } from 'slate' -import { DocumentRenderer } from '@keystone-6/document-renderer' import { - type CardValueComponent, type CellComponent, type FieldProps, } from '@keystone-6/core/types' @@ -69,13 +67,4 @@ export const Cell: CellComponent = ({ item, field, linkTo }) => { } Cell.supportsLinkTo = true -export const CardValue: CardValueComponent = ({ item, field }) => { - return ( - - {field.label} - - - ) -} - export const allowedExportsOnCustomViews = ['componentBlocks'] diff --git a/tests/api-tests/relationships/label-search-field-validation.test.ts b/tests/api-tests/relationships/label-search-field-validation.test.ts index 6123fa9aad9..d54d565463f 100644 --- a/tests/api-tests/relationships/label-search-field-validation.test.ts +++ b/tests/api-tests/relationships/label-search-field-validation.test.ts @@ -42,38 +42,6 @@ test("labelField that doesn't exist is rejected with displayMode: select", () => ) }) -test("labelField that doesn't exist is rejected with displayMode: cards", () => { - expect(() => - getContext( - ({ - db: { - provider: 'sqlite', - url: 'file://' - }, - lists: { - A: list({ - access: allowAll, - fields: { - something: relationship({ - ref: 'Thing', - ui: { - displayMode: 'cards', - cardFields: ['name'], - inlineConnect: { labelField: 'doesNotExist' }, - }, - }), - }, - }), - Thing, - }, - }), - {} as any - ) - ).toThrowErrorMatchingInlineSnapshot( - `"The ui.inlineConnect.labelField option for field 'A.something' uses 'doesNotExist' but that field doesn't exist."` - ) -}) - test("searchFields that don't exist are rejected with displayMode: select", () => { expect(() => getContext( @@ -104,38 +72,6 @@ test("searchFields that don't exist are rejected with displayMode: select", () = ) }) -test("searchFields that don't exist are rejected with displayMode: cards", () => { - expect(() => - getContext( - ({ - db: { - provider: 'sqlite', - url: 'file://' - }, - lists: { - A: list({ - access: allowAll, - fields: { - something: relationship({ - ref: 'Thing', - ui: { - displayMode: 'cards', - cardFields: ['name'], - inlineConnect: { labelField: 'name', searchFields: ['doesNotExist'] }, - }, - }), - }, - }), - Thing, - }, - }), - {} as any - ) - ).toThrowErrorMatchingInlineSnapshot( - `"The ui.inlineConnect.searchFields option for relationship field 'A.something' includes 'doesNotExist' but that field doesn't exist."` - ) -}) - test("searchFields that aren't searchable are rejected with displayMode: select", () => { expect(() => getContext( @@ -165,35 +101,3 @@ test("searchFields that aren't searchable are rejected with displayMode: select" `"The ui.searchFields option for field 'A.something' includes 'notText' but that field doesn't have a contains filter that accepts a GraphQL String"` ) }) - -test("searchFields that aren't searchable are rejected with displayMode: cards", () => { - expect(() => - getContext( - ({ - db: { - provider: 'sqlite', - url: 'file://' - }, - lists: { - A: list({ - access: allowAll, - fields: { - something: relationship({ - ref: 'Thing', - ui: { - displayMode: 'cards', - cardFields: ['name'], - inlineConnect: { labelField: 'name', searchFields: ['notText'] }, - }, - }), - }, - }), - Thing, - }, - }), - {} as any - ) - ).toThrowErrorMatchingInlineSnapshot( - `"The ui.searchFields option for field 'A.something' includes 'notText' but that field doesn't have a contains filter that accepts a GraphQL String"` - ) -}) diff --git a/tests/sandbox/configs/7590-add-item-to-relationship-in-hook-cards-ui.ts b/tests/sandbox/configs/7590-add-item-to-relationship-in-hook-cards-ui.ts deleted file mode 100644 index 5bd8853e16b..00000000000 --- a/tests/sandbox/configs/7590-add-item-to-relationship-in-hook-cards-ui.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { list, config } from '@keystone-6/core' -import { allowAll } from '@keystone-6/core/access' -import { relationship, text } from '@keystone-6/core/fields' -import { dbConfig } from '../utils' - -export const lists = { - User: list({ - access: allowAll, - fields: { - name: text(), - numbers: relationship({ - ref: 'Number.user', - many: true, - ui: { - displayMode: 'cards', - cardFields: ['value'], - }, - hooks: { - // every time you save, add a random number - async resolveInput (args) { - return { - ...args.resolvedData[args.fieldKey], - create: { - value: Math.floor(Math.random() * 100000).toString(), - }, - } - }, - }, - }), - }, - }), - Number: list({ - access: allowAll, - fields: { - user: relationship({ ref: 'User.numbers' }), - value: text({ validation: { isRequired: true } }), - }, - }), -} - -export default config({ - db: dbConfig, - lists -}) diff --git a/tests/sandbox/configs/all-the-things.ts b/tests/sandbox/configs/all-the-things.ts index 6ae9b9686f1..feba51a1bf0 100644 --- a/tests/sandbox/configs/all-the-things.ts +++ b/tests/sandbox/configs/all-the-things.ts @@ -26,10 +26,9 @@ import { schema as structureRelationshipsSchema } from '../structure-relationshi import { localStorageConfig, trackingFields } from '../utils' // import { type Lists } from '.keystone/types' // TODO -const description = - 'Some thing to describe to test the length of the text for width, blah blah blah blah blah blah blah blah blah' +const description = 'Some thing to describe to test the length of the text for width, blah blah blah blah blah blah blah blah blah' -export const lists = { +export const lists: any = { Thing: list({ access: allowAll, fields: { @@ -66,26 +65,12 @@ export const lists = { ref: 'User', ui: { description, - displayMode: 'cards', - cardFields: ['name', 'email'], - inlineConnect: { - labelField: 'email', - }, - inlineCreate: { fields: ['name', 'email'] }, - linkToItem: true, - inlineEdit: { fields: ['name', 'email'] }, }, }), toManyRelationshipCard: relationship({ ref: 'Todo', ui: { description, - displayMode: 'cards', - cardFields: ['label', 'isComplete', 'assignedTo'], - inlineConnect: true, - inlineCreate: { fields: ['label', 'isComplete', 'assignedTo'] }, - linkToItem: true, - inlineEdit: { fields: ['label', 'isComplete', 'assignedTo'] }, }, many: true, }),