Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual Input #9139

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions examples/example-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,29 @@ export const tasks = [
},
] as const

export const tags = [
{ "title": "Travel" },
{ "title": "Food" },
{ "title": "Technology" },
{ "title": "Wellness" },
{ "title": "Productivity" },
{ "title": "DIY" },
{ "title": "Lifestyle" },
{ "title": "Fashion" },
{ "title": "Fitness" },
{ "title": "Finance" },
{ "title": "Parenting" },
{ "title": "Home Decor" },
{ "title": "Self-Care" },
{ "title": "Recipes" },
{ "title": "Entrepreneurship" },
{ "title": "Book Reviews" },
{ "title": "Sustainable Living" },
{ "title": "Photography" },
{ "title": "Mental Health" },
{ "title": "Relationships" }
]

export const products = [
{
name: 'Yeti Hondo',
Expand Down
10 changes: 10 additions & 0 deletions examples/graphql-ts-gql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,13 @@ enum OrderDirection {
input PostUpdateInput {
title: String
status: PostStatusType
isPublished: JSON
content: String
counts: JSON
excerpt: JSON
publishDate: DateTime
author: AuthorRelateToOneForUpdateInput
authorName: JSON
}

input AuthorRelateToOneForUpdateInput {
Expand All @@ -135,9 +139,13 @@ input PostUpdateArgs {
input PostCreateInput {
title: String
status: PostStatusType
isPublished: JSON
content: String
counts: JSON
excerpt: JSON
publishDate: DateTime
author: AuthorRelateToOneForCreateInput
authorName: JSON
}

input AuthorRelateToOneForCreateInput {
Expand Down Expand Up @@ -185,6 +193,7 @@ input AuthorUpdateInput {
name: String
email: String
posts: PostRelateToManyForUpdateInput
latestPost: JSON
}

input PostRelateToManyForUpdateInput {
Expand All @@ -203,6 +212,7 @@ input AuthorCreateInput {
name: String
email: String
posts: PostRelateToManyForCreateInput
latestPost: JSON
}

input PostRelateToManyForCreateInput {
Expand Down
2 changes: 2 additions & 0 deletions examples/transactions/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ input ProductUpdateInput {
sku: String
description: String
value: Int
available: JSON
}

input ProductUpdateArgs {
Expand All @@ -241,6 +242,7 @@ input ProductCreateInput {
sku: String
description: String
value: Int
available: JSON
}

"""
Expand Down
2 changes: 2 additions & 0 deletions examples/usecase-relationship-union/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ input MediaOrderByInput {
}

input MediaUpdateInput {
label: JSON
description: String
post: PostRelateToOneForUpdateInput
link: LinkRelateToOneForUpdateInput
Expand All @@ -176,6 +177,7 @@ input MediaUpdateArgs {
}

input MediaCreateInput {
label: JSON
description: String
post: PostRelateToOneForCreateInput
link: LinkRelateToOneForCreateInput
Expand Down
44 changes: 44 additions & 0 deletions examples/virtual-field/fields/useFieldForeignListKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { gql, useQuery } from '@keystone-6/core/admin-ui/apollo';
import { DeepNullable, makeDataGetter } from '@keystone-6/core/admin-ui/utils';
import { useMemo } from 'react';

// Custom hook to fetch and provide the foreign list key for a field
export default function useFieldForeignListKey(listKey: string, fieldPath: string): {
foreignListKey: string; // The foreign list key obtained from the server
foreignLabelPath: string;
refetch: () => Promise<void>; // Function to refetch the query data
} {
// GraphQL query to get the foreign list key based on the provided listKey and fieldPath
const result = useQuery(
gql`
query FieldForeignListKey($listKey: String!, $fieldPath: String!) {
FieldForeignListKey(listKey: $listKey, fieldPath: $fieldPath) {
foreignListKey
foreignLabelPath
}
}
`,
{ variables: { listKey, fieldPath } } // Pass query variables
);

// Use useMemo to efficiently compute derived state based on the query result
return useMemo(() => {
// Function to refetch the query data
const refetch = async () => {
await result.refetch(); // Calls the refetch method on the result object
};

// Create a data getter object to safely access deeply nested GraphQL data
const dataGetter = makeDataGetter<DeepNullable<{ FieldForeignListKey: { foreignListKey: string; foreignLabelPath: string; } }>>(
result.data,
result.error?.graphQLErrors // Pass GraphQL errors if present
);

// Extract and return the foreign list key from the data or an empty string if unavailable
return {
foreignListKey: dataGetter.get('FieldForeignListKey').get('foreignListKey').data || '',
foreignLabelPath: dataGetter.get('FieldForeignListKey').get('foreignLabelPath').data || '',
refetch, // Include the refetch function in the return object
};
}, [result]); // Dependencies include the result object to recompute the memoized value
}
72 changes: 72 additions & 0 deletions examples/virtual-field/fields/virtual/tags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/** @jsxRuntime classic */
/** @jsx jsx */

import { jsx } from '@keystone-ui/core';
import { FieldProps } from '@keystone-6/core/types';
import { FieldContainer, FieldDescription, FieldLabel } from '@keystone-ui/fields';
import { controller } from '@keystone-6/core/fields/types/virtual/views';
import { AutocompleteSelect, OrderableList, type Item } from '../../src/primatives'
import useFieldForeignListKey from '../useFieldForeignListKey';

export const Field = (props: FieldProps<typeof controller>) => {
// Get metadata properties using a custom hook
const metaProps = useFieldForeignListKey(props.field.listKey, props.field.path);

// Initialize state with the provided value, defaulting to an empty array
const value = typeof props.value === 'object' ? props.value : [];

return (
<FieldContainer>
<FieldLabel>{props.field.label}</FieldLabel>
<FieldDescription id={`${props.field.path}-description`}>
{props.field.description}
</FieldDescription>

{(props.onChange && metaProps.foreignListKey && metaProps.foreignLabelPath) ? (
<ComponentWrapper
foreignListKey={metaProps.foreignListKey}
foreignLabelPath={metaProps.foreignLabelPath}
onChange={props.onChange}
value={value}
/>
) : null}
</FieldContainer>
);
};

function ComponentWrapper(props: {
foreignListKey: string;
foreignLabelPath: string;
value: any[];
onChange: (values: any) => void;
}) {
// Formatting the value to be typeof Item[]
const value: Item[] = props.value.map((v) => ({ label: v[props.foreignLabelPath], value: v.id }))

/* Handlers */
// Adds items only if they do not already share a label
const addItem = (item: Item) => {
if (value.filter((v) => v.label === item.label).length === 0) {
onChange([...value, item])
}
}
// Handles both the item state and the onChange
const onChange = (items: Item[]) => {
props.onChange(items.map(i => ({ [props.foreignLabelPath]: i.label, id: i.value })))
}

return (
<div>
<AutocompleteSelect
listKey={props.foreignListKey}
fieldPath={props.foreignLabelPath}
onChange={addItem}
ignoreValues={value.map((v) => v.label)}
/>
<OrderableList
items={value.map(i => ({ ...i, key: i.label }))}
onChange={onChange}
/>
</div>
)
}
11 changes: 10 additions & 1 deletion examples/virtual-field/keystone.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { config } from '@keystone-6/core'
import { fixPrismaPath } from '../example-utils'
import { lists } from './schema'
import { lists } from './src/schema'
import extendGraphqlSchema from './src/static-data'
import { type Context } from '.keystone/types'
import { seedDatabase } from './src/seed'

export default config({
db: {
provider: 'sqlite',
url: process.env.DATABASE_URL || 'file:./keystone-example.db',
async onConnect(context: Context) {
if (process.argv.includes('--seed-database')) {
await seedDatabase(context)
}
},

// WARNING: this is only needed for our monorepo examples, dont do this
...fixPrismaPath,
},
graphql: { extendGraphqlSchema },
lists,
})
5 changes: 4 additions & 1 deletion examples/virtual-field/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
"private": true,
"license": "MIT",
"scripts": {
"dev": "keystone dev",
"dev": "keystone dev --seed-database",
"start": "keystone start",
"build": "keystone build",
"postinstall": "keystone postinstall"
},
"dependencies": {
"@hello-pangea/dnd": "^16.6.0",
"@keystone-6/core": "workspace:^",
"@keystone-ui/core": "workspace:^",
"@keystone-ui/fields": "workspace:^",
"@prisma/client": "^5.0.0"
},
"devDependencies": {
Expand Down
Loading
Loading