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

chore(zui): move studio component definitions in zui as defaults #374

Merged
merged 5 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
4 changes: 2 additions & 2 deletions zui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@bpinternal/zui",
"version": "0.9.3",
"description": "An extension of Zod for working nicely with UIs and JSON Schemas",
"version": "0.10.0",
"description": "A fork of Zod with additional features",
"type": "module",
"source": "./src/index.ts",
"main": "./dist/index.js",
Expand Down
17 changes: 1 addition & 16 deletions zui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
14 changes: 4 additions & 10 deletions zui/src/ui/Form.tsx
Original file line number Diff line number Diff line change
@@ -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<UI extends UIComponentDefinitions = DefaultComponentDefinitions> = {
export type ZuiFormProps<UI extends UIComponentDefinitions = EmptyComponentDefinitions> = {
schema: JSONSchema
components: ZuiComponentMap<UI>
value: any
Expand All @@ -21,7 +15,7 @@ export type ZuiFormProps<UI extends UIComponentDefinitions = DefaultComponentDef
onValidation?: (validation: FormValidation) => void
}

export const ZuiForm = <UI extends UIComponentDefinitions = DefaultComponentDefinitions>({
export const ZuiForm = <UI extends UIComponentDefinitions = EmptyComponentDefinitions>({
schema,
components,
onChange,
Expand Down Expand Up @@ -53,7 +47,7 @@ export const ZuiForm = <UI extends UIComponentDefinitions = DefaultComponentDefi
>
<ErrorBoundary fallback={fallback} fieldSchema={schema} path={[]}>
<FormElementRenderer
components={components as any}
components={components}
fieldSchema={schema}
path={[]}
fallback={fallback}
Expand Down
155 changes: 155 additions & 0 deletions zui/src/ui/component-definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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(),
}),
},
franklevasseur marked this conversation as resolved.
Show resolved Hide resolved
json: {
id: 'json',
params: commonInputParams.extend({
showPreview: z.boolean().optional(),
showValidationError: z.boolean().optional(),
franklevasseur marked this conversation as resolved.
Show resolved Hide resolved
}),
},
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(),
}),
franklevasseur marked this conversation as resolved.
Show resolved Hide resolved
},
},
discriminatedUnion: {},
} as const satisfies UIComponentDefinitions

export type DefaultComponentDefinitions = typeof defaultComponentDefinitions
18 changes: 17 additions & 1 deletion zui/src/ui/index.ts
Original file line number Diff line number Diff line change
@@ -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'
20 changes: 10 additions & 10 deletions zui/src/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}
Expand Down Expand Up @@ -197,7 +197,7 @@ export type AsBaseType<T> = 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
Expand All @@ -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
Expand Down Expand Up @@ -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<Type, ID, UI> & {
discriminatorKey: string | null
discriminatorLabel: string
Expand All @@ -265,15 +265,15 @@ export type ZuiReactDiscriminatedUnionComponentProps<
export type ZuiReactObjectComponentProps<
Type extends ContainerType,
ID extends keyof UI[Type],
UI extends UIComponentDefinitions = DefaultComponentDefinitions,
UI extends UIComponentDefinitions = EmptyComponentDefinitions,
> = ZuiReactComponentBaseProps<Type, ID, UI> & {
children: JSX.Element | JSX.Element[]
}

export type ZuiReactArrayComponentProps<
Type extends ContainerType,
ID extends keyof UI[Type],
UI extends UIComponentDefinitions = DefaultComponentDefinitions,
UI extends UIComponentDefinitions = EmptyComponentDefinitions,
> = ZuiReactComponentBaseProps<Type, ID, UI> & {
children: JSX.Element | JSX.Element[]
addItem: (initialData?: any) => void
Expand All @@ -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<Type, ID, UI> & {
description?: string
required: boolean
Expand All @@ -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, ID, UI>
: Type extends 'array'
Expand All @@ -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<ZuiReactComponentProps<Type, ID, UI>>

export type ZuiComponentMap<UI extends UIComponentDefinitions = DefaultComponentDefinitions> = {
export type ZuiComponentMap<UI extends UIComponentDefinitions = EmptyComponentDefinitions> = {
[T in BaseType]: {
[K in keyof UI[T]]: ZuiReactComponent<T, K, UI>
} & {
Expand Down
3 changes: 2 additions & 1 deletion zui/src/ui/ui.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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'
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}` : ''}`
Expand Down
4 changes: 2 additions & 2 deletions zui/src/z/types/basetype/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
ZodKindToBaseType,
JSONSchema,
ParseSchema,
DefaultComponentDefinitions,
EmptyComponentDefinitions,
ZuiExtensionObject,
} from '../../../ui/types'
import { zuiKey } from '../../../ui/constants'
Expand Down Expand Up @@ -526,7 +526,7 @@ export abstract class ZodType<Output = any, Def extends ZodTypeDef = ZodTypeDef,
* The type of component to use to display the field and its options
*/
displayAs<
UI extends UIComponentDefinitions = DefaultComponentDefinitions,
UI extends UIComponentDefinitions = EmptyComponentDefinitions,
Type extends BaseType = ZodKindToBaseType<this['_def']>,
>(options: ParseSchema<UI[Type][keyof UI[Type]]>): this {
this._def[zuiKey] ??= {}
Expand Down
3 changes: 2 additions & 1 deletion zui/src/z/types/error/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TypeOf, ZodType, ZodFirstPartyTypeKind, defaultErrorMap, ZodParsedType, util, Primitive } from '../index'
import { errorMap as defaultErrorMap } from './locales/en'
import { TypeOf, ZodType, ZodFirstPartyTypeKind, ZodParsedType, util, Primitive } from '../index'

type allKeys<T> = T extends any ? keyof T : never

Expand Down
2 changes: 1 addition & 1 deletion zui/src/z/types/error/locales/en.ts
Original file line number Diff line number Diff line change
@@ -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:
Expand Down