diff --git a/.husky/pre-commit b/.husky/pre-commit index bb76f84..b14d371 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1 @@ -pnpm lint-staged -pnpm test +pnpm lint-staged && pnpm test diff --git a/src/components/AddFieldModal.tsx b/src/components/AddFieldModal.tsx index 550a764..110f5e7 100644 --- a/src/components/AddFieldModal.tsx +++ b/src/components/AddFieldModal.tsx @@ -4,7 +4,7 @@ import Form from '@rjsf/mui'; import validator from '@rjsf/validator-ajv8'; import React from 'react'; import { useSchema } from '../providers/SchemaProvider'; -import { JsonSchema, JsonSchemaType } from '../types'; +import { JsonSchema } from '../types'; import { JsonSchemaField } from '../fields/JsonSchemaField'; import Select from '@mui/material/Select'; import { Add } from '@mui/icons-material'; @@ -17,18 +17,38 @@ type Props = { const AddFieldModal = ({ parentPath }: Props) => { const [open, setOpen] = React.useState(false); const [name, setName] = React.useState(null); - const [type, setType] = React.useState(null); + const [type, setType] = React.useState(null); const [field, setField] = React.useState(null); - const { dispatch, fields } = useSchema(); + const { dispatch, fields, templates } = useSchema(); const [step, setStep] = React.useState(0); - const SelectedFieldClass = fields.find((f) => f.id === type)?.Class; - const handleSelectType = () => { + const SelectedFieldClass = fields.find((f) => f.id === type)?.Class; if (name && type && SelectedFieldClass) { setField(new SelectedFieldClass(name)); setStep(1); } + + const SelectedTemplateSchema = templates.find((f) => f.id === type)?.schema; + if (name && type && SelectedTemplateSchema) { + dispatch({ + type: 'ADD_PROPERTY', + payload: { + name: generatePath(parentPath, name || 'newField'), + schema: SelectedTemplateSchema, + }, + }); + dispatch({ + type: 'ADD_REQUIRED', + payload: { + name: generatePath(parentPath, name || 'newField'), + }, + }); + setOpen(false); + setType(null); + setName(null); + setStep(0); + } }; const handleSubmit = (formData: JsonSchema) => { @@ -85,18 +105,17 @@ const AddFieldModal = ({ parentPath }: Props) => { Field Type - setType(e.target.value)}> {fields.map((property) => ( {property.title} ))} + {templates?.map((property) => ( + + {property.title} + + ))} diff --git a/src/constants.ts b/src/constants.ts index 727e90b..019df52 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,7 +7,6 @@ import { ArrayField } from './fields/containers/ArrayField'; import { DateField } from './fields/widgets/DateField'; import { TimeField } from './fields/widgets/TimeField'; import { DateTimeField } from './fields/widgets/DateTimeField'; -import { FaqWidget } from './fields/patterns/FaqWidget'; import { FieldConfig } from './types'; import { SelectField } from './fields/widgets/SelectField'; @@ -89,18 +88,4 @@ export const STRING_WIDGETS: FieldConfig[] = [ }, ]; -export const PATTERNS: FieldConfig[] = [ - { - id: 'FAQ', - title: 'FAQ', - description: 'a FAQ form', - Class: FaqWidget, - }, -]; - -export const PROPERTIES = [ - ...PRIMITIVE_PROPERTIES, - ...CONTAINER_PROPERTIES, - // ...STRING_WIDGETS, - // ...PATTERNS, -]; +export const PROPERTIES = [...PRIMITIVE_PROPERTIES, ...CONTAINER_PROPERTIES, ...STRING_WIDGETS]; diff --git a/src/fields/patterns/FaqWidget.ts b/src/fields/patterns/FaqWidget.ts deleted file mode 100644 index f53c1fa..0000000 --- a/src/fields/patterns/FaqWidget.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { StringField } from '../primitives/StringField'; -import { ObjectField, ObjectFieldType } from '../containers/ObjectField'; -import { produce } from 'immer'; -import { ArrayField } from '../containers/ArrayField'; -import { JsonSchema } from '../../types'; -import { SCHEMA_TYPE } from '../../constants'; - -export type FaqType = ObjectFieldType; - -export class FaqWidget extends ArrayField { - constructor(name: string) { - super(name); - this.title = 'Frequently Asked Questions'; - this.setSchema({ - items: { - // @ts-expect-error TODO: fix - question: { - type: SCHEMA_TYPE.STRING, - title: 'FAQ Question', - }, - answer: { - type: SCHEMA_TYPE.STRING, - title: 'FAQ Answer', - }, - }, - }); - } - - getBuilderSchema(): JsonSchema { - // TODO: fix - return produce(super.getBuilderSchema(), (draft: JsonSchema) => { - const items = new ObjectField('items'); - items.setSchema({ - properties: { - question: new StringField('question').getBuilderSchema(), - answers: new StringField('answers').getBuilderSchema(), - }, - }); - if (draft?.items) draft.items = items.getBuilderSchema(); - }); - } -} diff --git a/src/providers/SchemaProvider.tsx b/src/providers/SchemaProvider.tsx index ed8041f..0119b26 100644 --- a/src/providers/SchemaProvider.tsx +++ b/src/providers/SchemaProvider.tsx @@ -1,6 +1,6 @@ import React, { createContext, Dispatch, ReactNode, useContext, useReducer } from 'react'; import { JsonSchemaBuilder } from '../builder/JsonSchemaBuilder'; -import { FieldConfig, JsonSchema } from '../types'; +import { FieldConfig, JsonSchema, TemplateType } from '../types'; import { PROPERTIES } from '../constants'; import type { RJSFSchema } from '@rjsf/utils'; @@ -13,10 +13,12 @@ export const SchemaContext = createContext<{ schema: JsonSchema; dispatch: Dispatch; fields: FieldConfig[]; + templates: TemplateType[]; }>({ schema: new JsonSchemaBuilder().setType('object').build(), dispatch: () => null, fields: [], + templates: [], }); const schemaReducer = (state: JsonSchema, action: SchemaAction): JsonSchema => { @@ -46,16 +48,17 @@ const schemaReducer = (state: JsonSchema, action: SchemaAction): JsonSchema => { }; type Props = { - extraFields: FieldConfig[]; children: ReactNode; value?: RJSFSchema; + templates?: TemplateType[]; + extraFields?: FieldConfig[]; }; -export const SchemaProvider = ({ children, extraFields, value }: Props) => { +export const SchemaProvider = ({ children, extraFields = [], value, templates = [] }: Props) => { const [schema, dispatch] = useReducer(schemaReducer, value || new JsonSchemaBuilder().setType('object').build()); return ( - + {children} ); diff --git a/src/stories/SchemaBuilder.stories.tsx b/src/stories/SchemaBuilder.stories.tsx index e33e8df..17f61da 100644 --- a/src/stories/SchemaBuilder.stories.tsx +++ b/src/stories/SchemaBuilder.stories.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import React from 'react'; -import { Story, Meta } from '@storybook/react'; +import { Story } from '@storybook/react'; import SchemaBuilder from '../components/SchemaBuilder'; import { STRING_WIDGETS } from '../constants'; import { SchemaProvider } from '../providers/SchemaProvider'; @@ -101,18 +101,18 @@ const sampleSchema: RJSFSchema = { export default { title: 'SchemaBuilder', component: SchemaBuilder, -} as Meta; +}; -const Template: Story = (args) => ( - +const PrimitivesTemplate: Story = (args) => ( + ); -export const Primitives = Template.bind({}); +export const Primitives = PrimitivesTemplate.bind({}); Primitives.args = {}; -export const Formats = Template.bind({}); +export const Formats = PrimitivesTemplate.bind({}); Formats.args = { extraFields: [...STRING_WIDGETS], }; @@ -158,3 +158,36 @@ Themed.args = { }, }, }; + +const customTemplate = { + id: 'FAQ_TEMPLATE', + title: 'FAQ Template', + description: 'A template schema for FAQ type.', + schema: { + title: 'FAQ', + type: 'array', + items: { + type: 'object', + title: 'List of Questions', + properties: { + question: { + title: 'question', + type: 'string', + }, + answer: { + title: 'answer', + type: 'string', + }, + }, + }, + uniqueItems: true, + }, +}; + +const FaqTemplate: Story = (args) => ( + + + +); + +export const CustomTemplate = FaqTemplate.bind({}); diff --git a/src/test/JsonSchemaBuilder.test.ts b/src/test/JsonSchemaBuilder.test.ts index 3e599ee..afb2314 100644 --- a/src/test/JsonSchemaBuilder.test.ts +++ b/src/test/JsonSchemaBuilder.test.ts @@ -165,4 +165,29 @@ describe('JsonSchemaBuilder', () => { const schema = builderWithInput.build(); expect(schema).toEqual(inputSchema); }); + + test('should add template schema', () => { + const templateSchema = { + title: 'FAQ', + type: 'array', + items: { + type: 'object', + title: 'List of Questions', + properties: { + question: { + title: 'question', + type: 'string', + }, + answer: { + title: 'answer', + type: 'string', + }, + }, + }, + uniqueItems: true, + }; + builder.addProperty('faq', templateSchema); + const schema = builder.build(); + expect(schema.properties?.faq).toEqual(templateSchema); + }); }); diff --git a/src/types.ts b/src/types.ts index 27d0e92..507bdd6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,6 +60,13 @@ export type FieldConfig = { Class: new (...args: string[]) => JsonSchemaField; // a class that extends JsonSchemaField }; +export type TemplateType = { + id: string; + title: string; + description: string; + schema: JsonSchema; +}; + export type DataVisualizationType = { schema: RJSFSchema; data: T;