Skip to content

Commit 8cb57dc

Browse files
authored
feat(typesync | create app form): switch to use @tanstack/react-form. component cleanup (#204)
1 parent f317c1f commit 8cb57dc

File tree

22 files changed

+667
-602
lines changed

22 files changed

+667
-602
lines changed

apps/typesync/client/src/Components/App/SchemaBuilder/SchemaBrowser.tsx renamed to apps/typesync/client/src/Components/App/CreateAppForm/SchemaBuilder/SchemaBrowser.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { useQuery } from '@tanstack/react-query';
66
import { Array as EffectArray, Order, pipe } from 'effect';
77
import { useState } from 'react';
88

9-
import type { SchemaBrowserTypesQuery } from '../../../generated/graphql';
10-
import { schemaBrowserQueryOptions } from '../../../hooks/useSchemaBrowserQuery';
11-
import { Loading } from '../../Loading';
9+
import type { SchemaBrowserTypesQuery } from '../../../../generated/graphql';
10+
import { schemaBrowserQueryOptions } from '../../../../hooks/useSchemaBrowserQuery';
11+
import { Loading } from '../../../Loading';
1212

1313
export type SchemaBrowserType = NonNullable<SchemaBrowserTypesQuery['space']>['types'][number];
1414
type ExtendedSchemaBrowserType = SchemaBrowserType & { slug: string };

apps/typesync/client/src/Components/App/SchemaBuilder/TypeCombobox.tsx renamed to apps/typesync/client/src/Components/App/CreateAppForm/SchemaBuilder/TypeCombobox.tsx

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
import { Label, Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react';
44
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/16/solid';
55
import { Array as EffectArray, String as EffectString, Schema, pipe } from 'effect';
6-
import type { UseFormSetValue } from 'react-hook-form';
76

8-
import type { InsertAppSchema } from '../../../schema.js';
9-
import { classnames } from '../../../utils/classnames.js';
7+
import { useFieldContext } from '../../../../context/form.js';
8+
import { classnames } from '../../../../utils/classnames.js';
109

1110
class TypeOption extends Schema.Class<TypeOption>('/hypergraph/typesync/models/TypeOption')({
1211
id: Schema.NonEmptyTrimmedString,
@@ -27,20 +26,12 @@ const typeOptions: Array<TypeOption> = [
2726
];
2827

2928
export function TypeCombobox({
30-
typePropertyIdx,
31-
typeIdx,
32-
value,
33-
onTypeSelected,
29+
id,
30+
name,
3431
schemaTypes = [],
3532
}: Readonly<{
36-
/** the index of this type selection field in the properties array. Types.AppSchemaForm.types[idx].properties[typeInputIdx] */
37-
typePropertyIdx: number;
38-
/** the index of the type within the schema array Types.AppSchemaForm.types[typeIdx] */
39-
typeIdx: number;
40-
/** the current value */
41-
value: string;
42-
/** set the value in the form when the user selects a value */
43-
onTypeSelected: UseFormSetValue<InsertAppSchema>;
33+
id: string;
34+
name: string;
4435
/**
4536
* A list of types within the defined schema that the user can use as a relation
4637
* This allows the user to specify the property as a relationship to a type in the schema
@@ -49,6 +40,8 @@ export function TypeCombobox({
4940
*/
5041
schemaTypes?: Array<string> | undefined;
5142
}>) {
43+
const field = useFieldContext<string>();
44+
5245
const relationTypeOptions = pipe(
5346
schemaTypes,
5447
EffectArray.filter((_type) => EffectString.isNonEmpty(_type)),
@@ -60,38 +53,37 @@ export function TypeCombobox({
6053
return (
6154
<Listbox
6255
as="div"
63-
id={`types.${typeIdx}.properties.${typePropertyIdx}.type_name`}
64-
name={`types.${typeIdx}.properties.${typePropertyIdx}.type_name`}
65-
value={value}
56+
id={id}
57+
name={name}
58+
value={field.state.value}
59+
onBlur={field.handleBlur}
6660
onChange={(value) => {
6761
if (value) {
68-
onTypeSelected(`types.${typeIdx}.properties.${typePropertyIdx}.type_name`, value, {
69-
shouldDirty: true,
70-
shouldTouch: true,
71-
shouldValidate: true,
72-
});
62+
field.handleChange(value);
7363
}
7464
}}
7565
>
7666
<Label className="sr-only">Prop type</Label>
7767
<div className="relative">
78-
<ListboxButton className="grid w-full cursor-default grid-cols-1 rounded-md bg-slate-900 py-1.5 pr-2 pl-3 text-left text-white outline-1 -outline-offset-1 outline-gray-500 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
79-
<span className="col-start-1 row-start-1 truncate pr-6">{value}</span>
68+
<ListboxButton className="grid w-full cursor-default grid-cols-1 rounded-md bg-white dakr:bg-slate-900 py-1.5 pr-2 pl-3 text-left text-white outline-1 -outline-offset-1 outline-gray-500 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
69+
<span className="col-start-1 row-start-1 truncate pr-6 text-gray-950 dark:text-white">
70+
{field.state.value}
71+
</span>
8072
<ChevronUpDownIcon
8173
aria-hidden="true"
82-
className="col-start-1 row-start-1 size-5 self-center justify-self-end text-gray-300 sm:size-4"
74+
className="col-start-1 row-start-1 size-5 self-center justify-self-end text-gray-300 dark:text-gray-50 sm:size-4"
8375
/>
8476
</ListboxButton>
8577

8678
<ListboxOptions
8779
transition
88-
className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-slate-800 py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-hidden data-leave:transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0 sm:text-sm"
80+
className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-gray-50 dark:bg-slate-800 py-1 text-base shadow-lg ring-1 ring-gray-200 dark:ring-black/5 focus:outline-hidden data-leave:transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0 sm:text-sm"
8981
>
9082
{typeOptions.map((type) => (
9183
<ListboxOption
9284
key={type.id}
9385
value={type.name}
94-
className="group relative cursor-default py-2 pr-9 pl-3 text-white select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden"
86+
className="group relative cursor-default py-2 pr-9 pl-3 text-gray-800 dark:text-white select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden"
9587
>
9688
<span className="block truncate font-normal group-data-selected:font-semibold">{type.name}</span>
9789

@@ -105,7 +97,7 @@ export function TypeCombobox({
10597
key={type.id}
10698
value={type.name}
10799
className={classnames(
108-
'group relative cursor-default py-2 pr-9 pl-3 text-white select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden',
100+
'group relative cursor-default py-2 pr-9 pl-3 text-gray-800 dark:text-white select-none data-focus:bg-indigo-600 data-focus:text-white data-focus:outline-hidden',
109101
idx === 0 ? 'border-t border-gray-400 dark:border-white/10' : '',
110102
)}
111103
>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createFormHook } from '@tanstack/react-form';
2+
3+
import { fieldContext, formContext } from '../../../context/form.js';
4+
import {
5+
FormComponentRadioGroup,
6+
FormComponentTextArea,
7+
FormComponentTextField,
8+
SubmitButton,
9+
} from '../../FormComponents/index.js';
10+
import { TypeCombobox } from './SchemaBuilder/TypeCombobox.js';
11+
12+
export const { useAppForm } = createFormHook({
13+
fieldComponents: {
14+
FormComponentTextField,
15+
FormComponentTextArea,
16+
FormComponentRadioGroup,
17+
TypeCombobox,
18+
},
19+
formComponents: {
20+
SubmitButton,
21+
},
22+
fieldContext,
23+
formContext,
24+
});

apps/typesync/client/src/Components/App/SchemaBuilder/SchemaPreview.tsx renamed to apps/typesync/client/src/Components/App/Schema/SchemaPreview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { useQuery } from '@tanstack/react-query';
44
import type { CSSProperties } from 'react';
55
import { type ThemedToken, codeToTokens } from 'shiki';
66

7+
import type { AppSchema } from '../../../schema.js';
78
import { classnames } from '../../../utils/classnames.js';
8-
import type * as Types from './types.js';
99
import * as Utils from './utils.js';
1010

1111
enum FontStyle {
@@ -20,7 +20,7 @@ enum FontStyle {
2020
type CodeChunk = ThemedToken;
2121
type CodeLine = { chunks: Array<CodeChunk>; style: 'added' | 'deleted' | null };
2222

23-
export function SchemaPreview({ schema }: Readonly<{ schema: Types.AppSchemaForm }>) {
23+
export function SchemaPreview({ schema }: Readonly<{ schema: AppSchema }>) {
2424
const { code, hash } = Utils.buildAppSchemaFormCode(schema);
2525
const { data } = useQuery({
2626
queryKey: ['App', 'schema', 'preview', hash] as const,

apps/typesync/client/src/Components/App/SchemaBuilder/utils.ts renamed to apps/typesync/client/src/Components/App/Schema/utils.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
import type { AppSchemaField, AppSchemaForm } from './types.js';
1+
import type { AppSchema } from '../../../schema.js';
22

33
function fieldToEntityString({
44
name,
5-
typeName,
5+
type_name,
66
nullable = false,
77
optional = false,
88
description,
9-
}: AppSchemaField): string {
9+
}: AppSchema['types'][number]['properties'][number]): string {
1010
// Add JSDoc comment if description exists
1111
const jsDoc = description ? ` /** ${description} */\n` : '';
1212

1313
// Convert type to Entity type
1414
const entityType = (() => {
1515
switch (true) {
16-
case typeName === 'Text':
16+
case type_name === 'Text':
1717
return 'Type.Text';
18-
case typeName === 'Number':
18+
case type_name === 'Number':
1919
return 'Type.Number';
20-
case typeName === 'Boolean':
20+
case type_name === 'Boolean':
2121
return 'Type.Boolean';
22-
case typeName === 'Date':
22+
case type_name === 'Date':
2323
return 'Type.Date';
24-
case typeName === 'Url':
24+
case type_name === 'Url':
2525
return 'Type.Url';
26-
case typeName === 'Point':
26+
case type_name === 'Point':
2727
return 'Type.Point';
28-
case typeName.startsWith('Relation'):
28+
case type_name.startsWith('Relation'):
2929
// renders the type as `Type.Relation(Entity)`
30-
return `Type.${typeName}`;
30+
return `Type.${type_name}`;
3131
default:
3232
// how to handle complex types
3333
return 'Type.Text';
@@ -46,7 +46,7 @@ function fieldToEntityString({
4646

4747
function typeDefinitionToString(type: {
4848
name: string;
49-
properties: Readonly<Array<AppSchemaField>>;
49+
properties: ReadonlyArray<AppSchema['types'][number]['properties'][number]>;
5050
}): string | null {
5151
if (!type.name) {
5252
return null;
@@ -97,7 +97,7 @@ ${fieldStrings.join(',\n')}
9797
* @param schema the app schema being built by the user
9898
* @returns a typescript string representation of the schema as well as a 20bit hash to pass to the useQuery hook
9999
*/
100-
export function buildAppSchemaFormCode(schema: AppSchemaForm): Readonly<{ code: string; hash: string }> {
100+
export function buildAppSchemaFormCode(schema: AppSchema): Readonly<{ code: string; hash: string }> {
101101
const fileCommentStatement = '// src/schema.ts';
102102
const importStatement = `import { Entity, Type } from '@graphprotocol/hypergraph';\nimport * as Schema from 'effect/Schema';`;
103103
const typeDefinitions = schema.types

0 commit comments

Comments
 (0)