From 28eaa77d661a1929d21e4cf59ad1743683511116 Mon Sep 17 00:00:00 2001 From: Francois Levasseur Date: Mon, 26 Aug 2024 14:16:41 -0400 Subject: [PATCH 1/5] chore(zui): move studio component definitions in zui as defaults --- zui/src/index.ts | 17 +--- zui/src/ui/Form.tsx | 12 +-- zui/src/ui/component-definitions.ts | 150 ++++++++++++++++++++++++++++ zui/src/ui/index.ts | 18 +++- zui/src/ui/types.ts | 20 ++-- zui/src/ui/ui.test.tsx | 3 +- zui/src/z/types/basetype/index.ts | 4 +- 7 files changed, 185 insertions(+), 39 deletions(-) create mode 100644 zui/src/ui/component-definitions.ts diff --git a/zui/src/index.ts b/zui/src/index.ts index f95a5c70..71fef3f7 100644 --- a/zui/src/index.ts +++ b/zui/src/index.ts @@ -7,22 +7,7 @@ import { TypescriptGenerationOptions, } from './transforms/zui-to-typescript-next' -export type { - BaseType, - UIComponentDefinitions, - ZuiComponentMap, - AsBaseType, - ZuiReactComponent, - DefaultComponentDefinitions, - ZuiReactComponentProps, - JSONSchema, - JSONSchemaOfType, - MergeUIComponentDefinitions, - FormValidation, - FormError, -} from './ui/types' -export type { BoundaryFallbackComponent } from './ui/ErrorBoundary' -export { ZuiForm, type ZuiFormProps } from './ui' +export * from './ui' export * from './z' export const transforms = { diff --git a/zui/src/ui/Form.tsx b/zui/src/ui/Form.tsx index 0f0a85b0..11cc789e 100644 --- a/zui/src/ui/Form.tsx +++ b/zui/src/ui/Form.tsx @@ -1,16 +1,10 @@ import React, { useState, useEffect } from 'react' import { BoundaryFallbackComponent, ErrorBoundary } from './ErrorBoundary' import { FormDataProvider, deepMerge, getDefaultValues } from './hooks/useFormData' -import { - FormValidation, - DefaultComponentDefinitions, - JSONSchema, - UIComponentDefinitions, - ZuiComponentMap, -} from './types' +import { FormValidation, EmptyComponentDefinitions, JSONSchema, UIComponentDefinitions, ZuiComponentMap } from './types' import { FormElementRenderer } from './ElementRenderer' -export type ZuiFormProps = { +export type ZuiFormProps = { schema: JSONSchema components: ZuiComponentMap value: any @@ -21,7 +15,7 @@ export type ZuiFormProps void } -export const ZuiForm = ({ +export const ZuiForm = ({ schema, components, onChange, diff --git a/zui/src/ui/component-definitions.ts b/zui/src/ui/component-definitions.ts new file mode 100644 index 00000000..6c8f8c70 --- /dev/null +++ b/zui/src/ui/component-definitions.ts @@ -0,0 +1,150 @@ +import z from '../z' +import { UIComponentDefinitions } from './types' + +const commonInputParams = z.object({ + allowDynamicVariable: z.boolean().optional(), + horizontal: z.boolean().optional(), +}) + +export const defaultComponentDefinitions = { + string: { + text: { + id: 'text', + params: commonInputParams.extend({ + multiLine: z.boolean().optional(), + growVertically: z.boolean().optional(), + suggestions: z.array(z.string()).optional(), + }), + }, + dropdown: { + id: 'dropdown', + params: commonInputParams.extend({ + filterable: z.boolean().optional(), + }), + }, + radiogroup: { + id: 'radiogroup', + params: commonInputParams.extend({}), + }, + date: { + id: 'date', + params: commonInputParams.extend({ + dateFormat: z.string().optional(), + minDate: z.string().optional(), + maxDate: z.string().optional(), + defaultTimezone: z.string().optional(), + disableTimezoneSelection: z.boolean().optional(), + highlightCurrentDay: z.boolean().optional(), + showShortcutButtons: z.boolean().optional(), + showOutsideDaysOfMonth: z.boolean().optional(), + firstDayOfWeek: z.number().optional(), + canChangeMonth: z.boolean().optional(), + showWeekNumbers: z.boolean().optional(), + }), + }, + time: { + id: 'time', + params: commonInputParams.extend({ + useAMPM: z.boolean().optional(), + timeFormat: z.string().optional(), + minTime: z.string().optional(), + maxTime: z.string().optional(), + showArrowButtons: z.boolean().optional(), + precision: z.enum(['minute', 'second', 'millisecond']).optional(), + }), + }, + richtext: { + id: 'richtext', + params: z.object({ + allowDynamicVariable: z.boolean().optional(), + resizable: z.boolean().optional(), + }), + }, + json: { + id: 'json', + params: commonInputParams.extend({ + showPreview: z.boolean().optional(), + showValidationError: z.boolean().optional(), + }), + }, + file: { + id: 'file', + params: commonInputParams.extend({ + fileTypes: z.array(z.enum(['image', 'audio', 'video'])).optional(), + showUploadedFiles: z.boolean().optional(), + }), + }, + }, + number: { + number: { + id: 'number', + params: commonInputParams.extend({ + allowNumericCharactersOnly: z.boolean().optional(), + stepSize: z.number().optional(), + }), + }, + slider: { + id: 'slider', + params: z.object({ + horizontal: z.boolean().optional(), + stepSize: z.number().optional(), + }), + }, + }, + boolean: { + switch: { + id: 'switch', + params: commonInputParams, + }, + }, + array: { + options: { + id: 'options', + params: commonInputParams, + }, + strings: { + id: 'strings', + params: commonInputParams, + }, + daterange: { + id: 'daterange', + params: z.object({ + dateFormat: z.string().optional(), + minDate: z.string().optional(), + maxDate: z.string().optional(), + defaultTimezone: z.string().optional(), + allowSingleDayRange: z.boolean().optional(), + highlightCurrentDay: z.boolean().optional(), + showOutsideDaysOfMonth: z.boolean().optional(), + firstDayOfWeek: z.number().optional(), + canChangeMonth: z.boolean().optional(), + showWeekNumbers: z.boolean().optional(), + }), + }, + }, + object: { + collapsible: { + id: 'collapsible', + params: z.object({ + defaultOpen: z.boolean().optional(), + }), + }, + modal: { + id: 'modal', + params: z.object({ + title: z.string().optional(), + buttonLabel: z.string().optional(), + closeButtonLabel: z.string().optional(), + }), + }, + popover: { + id: 'popover', + params: z.object({ + buttonLabel: z.string().optional(), + }), + }, + }, + discriminatedUnion: {}, +} as const satisfies UIComponentDefinitions + +export type DefaultComponentDefinitions = typeof defaultComponentDefinitions diff --git a/zui/src/ui/index.ts b/zui/src/ui/index.ts index 3f603e0b..765842d5 100644 --- a/zui/src/ui/index.ts +++ b/zui/src/ui/index.ts @@ -1,2 +1,18 @@ export { ZuiForm, type ZuiFormProps } from './Form' -export { getSchemaType } from './utils' + +export type { DefaultComponentDefinitions } from './component-definitions' +export type { BoundaryFallbackComponent } from './ErrorBoundary' + +export type { + BaseType, + UIComponentDefinitions, + ZuiComponentMap, + AsBaseType, + ZuiReactComponent, + ZuiReactComponentProps, + JSONSchema, + JSONSchemaOfType, + MergeUIComponentDefinitions, + FormValidation, + FormError, +} from './types' diff --git a/zui/src/ui/types.ts b/zui/src/ui/types.ts index 8cb2007e..da1b3f2a 100644 --- a/zui/src/ui/types.ts +++ b/zui/src/ui/types.ts @@ -133,7 +133,7 @@ export type BaseType = 'number' | 'string' | 'boolean' | 'object' | 'array' | 'd export const containerTypes = ['object', 'array', 'discriminatedUnion'] as const export type ContainerType = (typeof containerTypes)[number] -export type DefaultComponentDefinitions = { +export type EmptyComponentDefinitions = { number: {} string: {} boolean: {} @@ -197,7 +197,7 @@ export type AsBaseType = T extends BaseType ? T : never export type SchemaContext< Type extends BaseType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = { type: Type id: ID @@ -215,7 +215,7 @@ export type FormError = { export type ZuiReactComponentBaseProps< Type extends BaseType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = { type: Type componentID: ID @@ -252,7 +252,7 @@ export type ZuiReactArrayChildProps = export type ZuiReactDiscriminatedUnionComponentProps< Type extends ContainerType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = ZuiReactComponentBaseProps & { discriminatorKey: string | null discriminatorLabel: string @@ -265,7 +265,7 @@ export type ZuiReactDiscriminatedUnionComponentProps< export type ZuiReactObjectComponentProps< Type extends ContainerType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = ZuiReactComponentBaseProps & { children: JSX.Element | JSX.Element[] } @@ -273,7 +273,7 @@ export type ZuiReactObjectComponentProps< export type ZuiReactArrayComponentProps< Type extends ContainerType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = ZuiReactComponentBaseProps & { children: JSX.Element | JSX.Element[] addItem: (initialData?: any) => void @@ -283,7 +283,7 @@ export type ZuiReactArrayComponentProps< export type ZuiReactControlComponentProps< Type extends BaseType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = ZuiReactComponentBaseProps & { description?: string required: boolean @@ -293,7 +293,7 @@ export type ZuiReactControlComponentProps< export type ZuiReactComponentProps< Type extends BaseType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = Type extends 'object' ? ZuiReactObjectComponentProps : Type extends 'array' @@ -305,10 +305,10 @@ export type ZuiReactComponentProps< export type ZuiReactComponent< Type extends BaseType, ID extends keyof UI[Type], - UI extends UIComponentDefinitions = DefaultComponentDefinitions, + UI extends UIComponentDefinitions = EmptyComponentDefinitions, > = FC> -export type ZuiComponentMap = { +export type ZuiComponentMap = { [T in BaseType]: { [K in keyof UI[T]]: ZuiReactComponent } & { diff --git a/zui/src/ui/ui.test.tsx b/zui/src/ui/ui.test.tsx index 4d3effe9..143e2bbc 100644 --- a/zui/src/ui/ui.test.tsx +++ b/zui/src/ui/ui.test.tsx @@ -1,6 +1,6 @@ import React from 'react' import { fireEvent, render } from '@testing-library/react' -import { ZuiForm, ZuiFormProps, getSchemaType } from './index' +import { ZuiForm, ZuiFormProps } from './index' import { resolveDiscriminatedSchema, resolveDiscriminator } from './hooks/useDiscriminator' import { ZuiComponentMap } from '../index' import { ObjectSchema, JSONSchema, ZuiReactComponentBaseProps, BaseType, UIComponentDefinitions } from './types' @@ -8,6 +8,7 @@ import { FC, PropsWithChildren, useState } from 'react' import { vi } from 'vitest' import { z as zui } from '../z/index' import { zuiKey } from './constants' +import { getSchemaType } from './utils' const TestId = (type: BaseType, path: string[], subpath?: string) => `${type}:${path.length > 0 ? path.join('.') : ''}${subpath ? `:${subpath}` : ''}` diff --git a/zui/src/z/types/basetype/index.ts b/zui/src/z/types/basetype/index.ts index e650fc92..9c778ef9 100644 --- a/zui/src/z/types/basetype/index.ts +++ b/zui/src/z/types/basetype/index.ts @@ -4,7 +4,7 @@ import type { ZodKindToBaseType, JSONSchema, ParseSchema, - DefaultComponentDefinitions, + EmptyComponentDefinitions, ZuiExtensionObject, } from '../../../ui/types' import { zuiKey } from '../../../ui/constants' @@ -526,7 +526,7 @@ export abstract class ZodType, >(options: ParseSchema): this { this._def[zuiKey] ??= {} From 010025050ee576b652a0674793b194f689781129 Mon Sep 17 00:00:00 2001 From: Francois Levasseur Date: Mon, 26 Aug 2024 15:08:00 -0400 Subject: [PATCH 2/5] update --- zui/src/ui/Form.tsx | 2 +- zui/src/ui/component-definitions.ts | 7 ++++++- zui/src/z/types/error/index.ts | 3 ++- zui/src/z/types/error/locales/en.ts | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zui/src/ui/Form.tsx b/zui/src/ui/Form.tsx index 11cc789e..3eda967e 100644 --- a/zui/src/ui/Form.tsx +++ b/zui/src/ui/Form.tsx @@ -47,7 +47,7 @@ export const ZuiForm = = T extends any ? keyof T : never diff --git a/zui/src/z/types/error/locales/en.ts b/zui/src/z/types/error/locales/en.ts index a46f135e..9665926a 100644 --- a/zui/src/z/types/error/locales/en.ts +++ b/zui/src/z/types/error/locales/en.ts @@ -1,6 +1,6 @@ import { type ZodErrorMap, util, ZodIssueCode, ZodParsedType } from '../../index' -const errorMap: ZodErrorMap = (issue, _ctx) => { +export const errorMap: ZodErrorMap = (issue, _ctx) => { let message: string switch (issue.code) { case ZodIssueCode.invalid_type: From 83ffd3c316f7070199208aae52795a922ea03d59 Mon Sep 17 00:00:00 2001 From: Francois Levasseur Date: Mon, 26 Aug 2024 16:41:49 -0400 Subject: [PATCH 3/5] update --- zui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zui/package.json b/zui/package.json index cc086154..47b74aa4 100644 --- a/zui/package.json +++ b/zui/package.json @@ -1,6 +1,6 @@ { "name": "@bpinternal/zui", - "version": "0.9.3", + "version": "0.10.0", "description": "An extension of Zod for working nicely with UIs and JSON Schemas", "type": "module", "source": "./src/index.ts", From f829b4f3dd048c377ca5663d799c40f09c1836c2 Mon Sep 17 00:00:00 2001 From: Francois Levasseur Date: Mon, 26 Aug 2024 16:44:35 -0400 Subject: [PATCH 4/5] update --- zui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zui/package.json b/zui/package.json index 47b74aa4..4461242b 100644 --- a/zui/package.json +++ b/zui/package.json @@ -1,7 +1,7 @@ { "name": "@bpinternal/zui", "version": "0.10.0", - "description": "An extension of Zod for working nicely with UIs and JSON Schemas", + "description": "A fork of Zod with additional features", "type": "module", "source": "./src/index.ts", "main": "./dist/index.js", From 1c30eb17a450b4a3c2ff2e40f024ae08537157f6 Mon Sep 17 00:00:00 2001 From: Francois Levasseur Date: Tue, 27 Aug 2024 15:33:13 -0400 Subject: [PATCH 5/5] update --- zui/src/ui/component-definitions.ts | 144 ++-------------------------- zui/src/ui/index.ts | 4 +- zui/src/ui/types.ts | 5 +- 3 files changed, 10 insertions(+), 143 deletions(-) diff --git a/zui/src/ui/component-definitions.ts b/zui/src/ui/component-definitions.ts index d45ebb3a..60a84fd2 100644 --- a/zui/src/ui/component-definitions.ts +++ b/zui/src/ui/component-definitions.ts @@ -1,154 +1,22 @@ import z from '../z' import { UIComponentDefinitions } from './types' -const commonInputParams = z.object({ - /** - * TODO: remove this field (allowDynamicVariable); - * - the schema developer should not be responsible for allowing dynamic variables or not - * - the dev who renders the schema should be the one with enough context to decide if dynamic variables are allowed - */ - allowDynamicVariable: z.boolean().optional(), - horizontal: z.boolean().optional(), -}) - -const defaultComponentDefinitions = { - string: { - text: { - id: 'text', - params: commonInputParams.extend({ - multiLine: z.boolean().optional(), - growVertically: z.boolean().optional(), - suggestions: z.array(z.string()).optional(), - }), - }, - dropdown: { - id: 'dropdown', - params: commonInputParams.extend({ - filterable: z.boolean().optional(), - }), - }, - radiogroup: { - id: 'radiogroup', - params: commonInputParams.extend({}), - }, - date: { - id: 'date', - params: commonInputParams.extend({ - dateFormat: z.string().optional(), - minDate: z.string().optional(), - maxDate: z.string().optional(), - defaultTimezone: z.string().optional(), - disableTimezoneSelection: z.boolean().optional(), - highlightCurrentDay: z.boolean().optional(), - showShortcutButtons: z.boolean().optional(), - showOutsideDaysOfMonth: z.boolean().optional(), - firstDayOfWeek: z.number().optional(), - canChangeMonth: z.boolean().optional(), - showWeekNumbers: z.boolean().optional(), - }), - }, - time: { - id: 'time', - params: commonInputParams.extend({ - useAMPM: z.boolean().optional(), - timeFormat: z.string().optional(), - minTime: z.string().optional(), - maxTime: z.string().optional(), - showArrowButtons: z.boolean().optional(), - precision: z.enum(['minute', 'second', 'millisecond']).optional(), - }), - }, - richtext: { - id: 'richtext', - params: z.object({ - allowDynamicVariable: z.boolean().optional(), - resizable: z.boolean().optional(), - }), - }, - json: { - id: 'json', - params: commonInputParams.extend({ - showPreview: z.boolean().optional(), - showValidationError: z.boolean().optional(), - }), - }, - file: { - id: 'file', - params: commonInputParams.extend({ - fileTypes: z.array(z.enum(['image', 'audio', 'video'])).optional(), - showUploadedFiles: z.boolean().optional(), - }), - }, - }, +export const defaultComponentDefinitions = { + string: {}, number: { - number: { - id: 'number', - params: commonInputParams.extend({ - allowNumericCharactersOnly: z.boolean().optional(), - stepSize: z.number().optional(), - }), - }, slider: { id: 'slider', - params: z.object({ - horizontal: z.boolean().optional(), - stepSize: z.number().optional(), - }), + params: z.object({}), }, }, boolean: { switch: { id: 'switch', - params: commonInputParams, - }, - }, - array: { - options: { - id: 'options', - params: commonInputParams, - }, - strings: { - id: 'strings', - params: commonInputParams, - }, - daterange: { - id: 'daterange', - params: z.object({ - dateFormat: z.string().optional(), - minDate: z.string().optional(), - maxDate: z.string().optional(), - defaultTimezone: z.string().optional(), - allowSingleDayRange: z.boolean().optional(), - highlightCurrentDay: z.boolean().optional(), - showOutsideDaysOfMonth: z.boolean().optional(), - firstDayOfWeek: z.number().optional(), - canChangeMonth: z.boolean().optional(), - showWeekNumbers: z.boolean().optional(), - }), - }, - }, - object: { - collapsible: { - id: 'collapsible', - params: z.object({ - defaultOpen: z.boolean().optional(), - }), - }, - modal: { - id: 'modal', - params: z.object({ - title: z.string().optional(), - buttonLabel: z.string().optional(), - closeButtonLabel: z.string().optional(), - }), - }, - popover: { - id: 'popover', - params: z.object({ - buttonLabel: z.string().optional(), - }), + params: z.object({}), }, }, + array: {}, + object: {}, discriminatedUnion: {}, } as const satisfies UIComponentDefinitions diff --git a/zui/src/ui/index.ts b/zui/src/ui/index.ts index 765842d5..005b8ea8 100644 --- a/zui/src/ui/index.ts +++ b/zui/src/ui/index.ts @@ -1,7 +1,7 @@ export { ZuiForm, type ZuiFormProps } from './Form' -export type { DefaultComponentDefinitions } from './component-definitions' -export type { BoundaryFallbackComponent } from './ErrorBoundary' +export * from './component-definitions' +export * from './ErrorBoundary' export type { BaseType, diff --git a/zui/src/ui/types.ts b/zui/src/ui/types.ts index da1b3f2a..60e31c5c 100644 --- a/zui/src/ui/types.ts +++ b/zui/src/ui/types.ts @@ -325,10 +325,9 @@ export type ParseSchema = I extends infer U : object : never +export type Merge = Omit & B export type MergeUIComponentDefinitions = { - [Type in BaseType]: { - [K in keyof (T[Type] & U[Type])]: (T[Type] & U[Type])[K] - } + [Type in BaseType]: Merge } export type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T