mirror of
https://github.com/enso-org/enso.git
synced 2025-01-03 18:38:08 +03:00
Form Component (#9995)
This PR provides initial support for `Form` component and is supposed to be a first step in the long run. Over the next iterations, we're going to continue adding new features that support `<Form />` out of the box(inputs, checkboxes, and so on) Current PR is focused on providing the first version of Form component As a tech stack, we chose: 1. `react-hook-form` for being mature and feature-complete and performant 2. Zod as validation library instead of ajv(that is present in the project already) for smaller bundle size, simpler and ts-friendly configuration, and better support
This commit is contained in:
parent
65f28c322a
commit
1991aab19d
@ -34,26 +34,29 @@
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@hookform/resolvers": "^3.4.0",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@sentry/react": "^7.74.0",
|
||||
"@tanstack/react-query": "^5.27.5",
|
||||
"@tanstack/react-query": "5.37.1",
|
||||
"ajv": "^8.12.0",
|
||||
"clsx": "^1.1.1",
|
||||
"enso-common": "^1.0.0",
|
||||
"is-network-error": "^1.0.1",
|
||||
"monaco-editor": "0.47.0",
|
||||
"react": "^18.2.0",
|
||||
"react-aria": "^3.32.1",
|
||||
"react-aria-components": "^1.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-stately": "^3.30.1",
|
||||
"monaco-editor": "0.48.0",
|
||||
"react": "^18.3.1",
|
||||
"react-aria": "^3.33.0",
|
||||
"react-aria-components": "^1.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "4.0.13",
|
||||
"react-hook-form": "^7.51.4",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-stately": "^3.31.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"ts-results": "^3.3.0",
|
||||
"validator": "^13.11.0",
|
||||
"react-error-boundary": "4.0.13"
|
||||
"validator": "^13.12.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||
@ -64,6 +67,7 @@
|
||||
"@playwright/experimental-ct-react": "^1.40.0",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@react-types/shared": "^3.22.1",
|
||||
"@tanstack/react-query-devtools": "5.37.1",
|
||||
"@types/node": "^20.11.21",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
@ -71,7 +75,6 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@tanstack/react-query-devtools": "^5.36.2",
|
||||
"chalk": "^5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"enso-chat": "git://github.com/enso-org/enso-bot",
|
||||
@ -88,8 +91,8 @@
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"react-toastify": "^9.1.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss-react-aria-components": "^1.1.1",
|
||||
"tailwindcss-animate": "1.0.7",
|
||||
"tailwindcss-react-aria-components": "^1.1.1",
|
||||
"ts-plugin-namespace-auto-import": "^1.0.0",
|
||||
"typescript": "~5.2.2",
|
||||
"vite": "^4.4.9",
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as reactQuery from '@tanstack/react-query'
|
||||
import * as reactQueryDevtools from '@tanstack/react-query-devtools'
|
||||
|
||||
const ReactQueryDevtoolsProduction = React.lazy(() =>
|
||||
@ -19,6 +20,11 @@ const ReactQueryDevtoolsProduction = React.lazy(() =>
|
||||
*/
|
||||
export function ReactQueryDevtools() {
|
||||
const [showDevtools, setShowDevtools] = React.useState(false)
|
||||
// It's safer to pass the client directly to the devtools
|
||||
// since there might be a chance that we have multiple versions of react-query,
|
||||
// in case we forgot to update the devtools, npm messed up the versions,
|
||||
// or there are hoisting issues.
|
||||
const client = reactQuery.useQueryClient()
|
||||
|
||||
React.useEffect(() => {
|
||||
window.toggleDevtools = () => {
|
||||
@ -28,11 +34,11 @@ export function ReactQueryDevtools() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<reactQueryDevtools.ReactQueryDevtools />
|
||||
<reactQueryDevtools.ReactQueryDevtools client={client} />
|
||||
|
||||
{showDevtools && (
|
||||
<React.Suspense fallback={null}>
|
||||
<ReactQueryDevtoolsProduction />
|
||||
<ReactQueryDevtoolsProduction client={client} />
|
||||
</React.Suspense>
|
||||
)}
|
||||
</>
|
||||
|
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Form component
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as reactQuery from '@tanstack/react-query'
|
||||
import * as reactHookForm from 'react-hook-form'
|
||||
|
||||
import * as textProvider from '#/providers/TextProvider'
|
||||
|
||||
import * as components from './components'
|
||||
import type * as types from './types'
|
||||
|
||||
/**
|
||||
* Form component. It wraps the form and provides the form context.
|
||||
* It also handles the form submission.
|
||||
* Provides better error handling and form state management.
|
||||
* And serves a better UX out of the box.
|
||||
*
|
||||
* ## Component is in BETA and will be improved in the future.
|
||||
*/
|
||||
// There is no way to avoid type casting here
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const Form = React.forwardRef(function Form<
|
||||
TFieldValues extends reactHookForm.FieldValues,
|
||||
// This type is defined on library level and we can't change it
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends reactHookForm.FieldValues | undefined = undefined,
|
||||
>(props: types.FormProps<TFieldValues, TTransformedValues>, ref: React.Ref<HTMLFormElement>) {
|
||||
const formId = React.useId()
|
||||
|
||||
const {
|
||||
children,
|
||||
onSubmit,
|
||||
formRef,
|
||||
form,
|
||||
formOptions = {},
|
||||
className,
|
||||
style,
|
||||
onSubmitted = () => {},
|
||||
onSubmitSuccess = () => {},
|
||||
onSubmitFailed = () => {},
|
||||
id = formId,
|
||||
schema,
|
||||
...formProps
|
||||
} = props
|
||||
|
||||
const { getText } = textProvider.useText()
|
||||
|
||||
const innerForm = components.useForm<TFieldValues, TTransformedValues>(
|
||||
form ?? {
|
||||
...formOptions,
|
||||
...(schema ? { schema } : {}),
|
||||
}
|
||||
)
|
||||
|
||||
React.useImperativeHandle(formRef, () => innerForm, [innerForm])
|
||||
|
||||
const formMutation = reactQuery.useMutation({
|
||||
mutationKey: ['FormSubmit', id],
|
||||
mutationFn: async (fieldValues: TFieldValues) => {
|
||||
try {
|
||||
await onSubmit(fieldValues, innerForm)
|
||||
} catch (error) {
|
||||
innerForm.setError('root.submit', {
|
||||
message: error instanceof Error ? error.message : getText('arbitraryFormErrorMessage'),
|
||||
})
|
||||
// TODO: Should we throw the error here?
|
||||
// Or should we just log it?
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
throw error
|
||||
}
|
||||
},
|
||||
onError: onSubmitFailed,
|
||||
onSuccess: onSubmitSuccess,
|
||||
onMutate: onSubmitted,
|
||||
onSettled: onSubmitted,
|
||||
})
|
||||
|
||||
// There is no way to avoid type casting here
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,no-restricted-syntax,@typescript-eslint/no-unsafe-argument
|
||||
const formOnSubmit = innerForm.handleSubmit(formMutation.mutateAsync as any)
|
||||
|
||||
const formStateRenderProps = {
|
||||
formState: innerForm.formState,
|
||||
register: innerForm.register,
|
||||
unregister: innerForm.unregister,
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
id={id}
|
||||
ref={ref}
|
||||
onSubmit={formOnSubmit}
|
||||
className={typeof className === 'function' ? className(formStateRenderProps) : className}
|
||||
style={typeof style === 'function' ? style(formStateRenderProps) : style}
|
||||
noValidate
|
||||
{...formProps}
|
||||
>
|
||||
<reactHookForm.FormProvider {...innerForm}>
|
||||
{typeof children === 'function' ? children(formStateRenderProps) : children}
|
||||
</reactHookForm.FormProvider>
|
||||
</form>
|
||||
)
|
||||
}) as unknown as (<
|
||||
TFieldValues extends reactHookForm.FieldValues,
|
||||
// The type is defined on library level and we can't change it
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends reactHookForm.FieldValues | undefined = undefined,
|
||||
>(
|
||||
props: React.RefAttributes<HTMLFormElement> & types.FormProps<TFieldValues, TTransformedValues>
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
) => React.JSX.Element) & {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
schema: typeof components.schema
|
||||
useForm: typeof components.useForm
|
||||
Submit: typeof components.Submit
|
||||
Reset: typeof components.Reset
|
||||
FormError: typeof components.FormError
|
||||
useFormSchema: typeof components.useFormSchema
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}
|
||||
|
||||
Form.schema = components.schema
|
||||
Form.useForm = components.useForm
|
||||
Form.Submit = components.Submit
|
||||
Form.Reset = components.Reset
|
||||
Form.FormError = components.FormError
|
||||
Form.useFormSchema = components.useFormSchema
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Form error component.
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import * as reactHookForm from 'react-hook-form'
|
||||
|
||||
import * as textProvider from '#/providers/TextProvider'
|
||||
|
||||
import * as reactAriaComponents from '#/components/AriaComponents'
|
||||
|
||||
import type * as types from '../types'
|
||||
|
||||
/**
|
||||
* Props for the FormError component.
|
||||
*/
|
||||
export interface FormErrorProps<
|
||||
TFieldValues extends types.FieldValues,
|
||||
TTransformedFieldValues extends types.FieldValues,
|
||||
> extends Omit<reactAriaComponents.AlertProps, 'children'> {
|
||||
readonly form?: reactHookForm.UseFormReturn<TFieldValues, unknown, TTransformedFieldValues>
|
||||
}
|
||||
|
||||
/**
|
||||
* Form error component.
|
||||
*/
|
||||
export function FormError<
|
||||
TFieldValues extends types.FieldValues,
|
||||
TTransformedFieldValues extends types.FieldValues,
|
||||
>(props: FormErrorProps<TFieldValues, TTransformedFieldValues>) {
|
||||
const {
|
||||
form = reactHookForm.useFormContext(),
|
||||
size = 'medium',
|
||||
variant = 'error',
|
||||
...alertProps
|
||||
} = props
|
||||
|
||||
const { formState } = form
|
||||
const { errors } = formState
|
||||
const { getText } = textProvider.useText()
|
||||
|
||||
/**
|
||||
* Get the error message.
|
||||
*/
|
||||
const getSubmitError = (): string | null => {
|
||||
const formErrors = errors.root
|
||||
|
||||
if (formErrors) {
|
||||
const submitError = formErrors.submit
|
||||
|
||||
if (submitError) {
|
||||
return (
|
||||
submitError.message ??
|
||||
getText('arbitraryErrorTitle') + '. ' + getText('arbitraryErrorSubtitle')
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const errorMessage = getSubmitError()
|
||||
|
||||
return errorMessage != null ? (
|
||||
<reactAriaComponents.Alert size={size} variant={variant} {...alertProps}>
|
||||
{errorMessage}
|
||||
</reactAriaComponents.Alert>
|
||||
) : null
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Reset button for forms.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as reactHookForm from 'react-hook-form'
|
||||
|
||||
import * as ariaComponents from '#/components/AriaComponents'
|
||||
|
||||
/**
|
||||
* Props for the Reset component.
|
||||
*/
|
||||
export interface ResetProps extends Omit<ariaComponents.ButtonProps, 'loading'> {
|
||||
/**
|
||||
* Connects the submit button to a form.
|
||||
* If not provided, the button will use the nearest form context.
|
||||
*
|
||||
* This field is helpful when you need to use the submit button outside of the form.
|
||||
*/
|
||||
// For this component, we don't need to know the form fields
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly form?: reactHookForm.UseFormReturn<any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset button for forms.
|
||||
*/
|
||||
export function Reset(props: ResetProps): React.JSX.Element {
|
||||
const { form = reactHookForm.useFormContext(), variant = 'cancel', size = 'medium' } = props
|
||||
const { formState } = form
|
||||
|
||||
return (
|
||||
<ariaComponents.Button
|
||||
{...props}
|
||||
variant={variant}
|
||||
size={size}
|
||||
isDisabled={formState.isSubmitting}
|
||||
onPress={form.reset}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Submit button for forms.
|
||||
* Manages the form state and displays a loading spinner when the form is submitting.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as reactHookForm from 'react-hook-form'
|
||||
|
||||
import * as ariaComponents from '#/components/AriaComponents'
|
||||
|
||||
/**
|
||||
* Additional props for the Submit component.
|
||||
*/
|
||||
interface SubmitButtonBaseProps {
|
||||
readonly variant?: ariaComponents.ButtonProps['variant']
|
||||
/**
|
||||
* Connects the submit button to a form.
|
||||
* If not provided, the button will use the nearest form context.
|
||||
*
|
||||
* This field is helpful when you need to use the submit button outside of the form.
|
||||
*/
|
||||
// For this component, we don't need to know the form fields
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly form?: reactHookForm.UseFormReturn<any>
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the Submit component.
|
||||
*/
|
||||
export type SubmitProps = Omit<ariaComponents.ButtonProps, 'loading' | 'variant'> &
|
||||
SubmitButtonBaseProps
|
||||
|
||||
/**
|
||||
* Submit button for forms.
|
||||
*
|
||||
* Manages the form state and displays a loading spinner when the form is submitting.
|
||||
*/
|
||||
export function Submit(props: SubmitProps): React.JSX.Element {
|
||||
const { form = reactHookForm.useFormContext(), variant = 'submit', size = 'medium' } = props
|
||||
const { formState } = form
|
||||
|
||||
return (
|
||||
<ariaComponents.Button
|
||||
{...props}
|
||||
type="submit"
|
||||
variant={variant}
|
||||
size={size}
|
||||
loading={formState.isSubmitting}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Barrel file for form components.
|
||||
*/
|
||||
export * from './Submit'
|
||||
export * from './Reset'
|
||||
export * from './useForm'
|
||||
export * from './FormError'
|
||||
export * from './types'
|
||||
export * from './useFormSchema'
|
||||
export * from './schema'
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Create a schema for a form
|
||||
*/
|
||||
|
||||
export * as schema from 'zod'
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file
|
||||
* Types for the Form component.
|
||||
*/
|
||||
|
||||
import type * as reactHookForm from 'react-hook-form'
|
||||
import type * as z from 'zod'
|
||||
|
||||
/**
|
||||
* Field Values type.
|
||||
*/
|
||||
export type FieldValues = reactHookForm.FieldValues
|
||||
|
||||
/**
|
||||
* Props for the useForm hook.
|
||||
*/
|
||||
export interface UseFormProps<T extends FieldValues>
|
||||
extends Omit<reactHookForm.UseFormProps<T>, 'resetOptions' | 'resolver'> {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
readonly schema?: z.ZodObject<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type of the useForm hook.
|
||||
*/
|
||||
export type UseFormReturn<
|
||||
TFieldValues extends Record<string, unknown>,
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends Record<string, unknown> | undefined = undefined,
|
||||
> = reactHookForm.UseFormReturn<TFieldValues, unknown, TTransformedValues>
|
||||
|
||||
/**
|
||||
* Form State type.
|
||||
*/
|
||||
export type FormState<TFieldValues extends FieldValues> = reactHookForm.FormState<TFieldValues>
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* A hook that returns a form instance.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as zodResolver from '@hookform/resolvers/zod'
|
||||
import * as reactHookForm from 'react-hook-form'
|
||||
import invariant from 'tiny-invariant'
|
||||
|
||||
import type * as types from './types'
|
||||
|
||||
/**
|
||||
* A hook that returns a form instance.
|
||||
* @param optionsOrFormInstance - Either form options or a form instance
|
||||
*
|
||||
* If form instance is passed, it will be returned as is
|
||||
* If form options are passed, a form instance will be created and returned
|
||||
*
|
||||
* ***Note:*** This hook accepts either a form instance(If form is created outside)
|
||||
* or form options(and creates a form instance).
|
||||
* This is useful when you want to create a form instance outside the component
|
||||
* and pass it to the component.
|
||||
* But be careful, You should not switch between the two types of arguments.
|
||||
* Otherwise you'll be fired
|
||||
*/
|
||||
export function useForm<
|
||||
T extends types.FieldValues,
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends types.FieldValues | undefined = undefined,
|
||||
>(
|
||||
optionsOrFormInstance: types.UseFormProps<T> | types.UseFormReturn<T, TTransformedValues>
|
||||
): types.UseFormReturn<T, TTransformedValues> {
|
||||
const initialTypePassed = React.useRef(getArgsType(optionsOrFormInstance))
|
||||
|
||||
const argsType = getArgsType(optionsOrFormInstance)
|
||||
|
||||
invariant(
|
||||
initialTypePassed.current === argsType,
|
||||
`
|
||||
Found a switch between form options and form instance. This is not allowed. Please use either form options or form instance and stick to it.\n\n
|
||||
Initially passed: ${initialTypePassed.current}, Currently passed: ${argsType}.
|
||||
`
|
||||
)
|
||||
|
||||
if ('formState' in optionsOrFormInstance) {
|
||||
return optionsOrFormInstance
|
||||
} else {
|
||||
const { schema, ...options } = optionsOrFormInstance
|
||||
|
||||
return reactHookForm.useForm({
|
||||
...options,
|
||||
...(schema ? { resolver: zodResolver.zodResolver(schema) } : {}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of arguments passed to the useForm hook
|
||||
*/
|
||||
function getArgsType<
|
||||
T extends Record<string, unknown>,
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends Record<string, unknown> | undefined = undefined,
|
||||
>(args: types.UseFormProps<T> | types.UseFormReturn<T, TTransformedValues>) {
|
||||
return 'formState' in args ? 'formInstance' : 'formOptions'
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @file This file contains the useFormSchema hook for creating form schemas.
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import * as callbackEventHooks from '#/hooks/eventCallbackHooks'
|
||||
|
||||
import * as schemaComponent from './schema'
|
||||
import type * as types from './types'
|
||||
|
||||
/**
|
||||
* Hook to create a form schema.
|
||||
*/
|
||||
export function useFormSchema<T extends types.FieldValues>(
|
||||
callback: (schema: typeof schemaComponent.schema) => schemaComponent.schema.ZodObject<T>
|
||||
): schemaComponent.schema.ZodObject<T> {
|
||||
const callbackEvent = callbackEventHooks.useEventCallback(callback)
|
||||
return React.useMemo(() => callbackEvent(schemaComponent.schema), [callbackEvent])
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Barrel export file for Form components.
|
||||
*/
|
||||
|
||||
export * from './Form'
|
||||
export type * from './types'
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @file
|
||||
* Types for the Form component.
|
||||
*/
|
||||
|
||||
import type * as reactHookForm from 'react-hook-form'
|
||||
import type * as z from 'zod'
|
||||
|
||||
import type * as components from './components'
|
||||
|
||||
export type * from './components'
|
||||
|
||||
/**
|
||||
* Props for the Form component
|
||||
*/
|
||||
export type FormProps<
|
||||
TFieldValues extends components.FieldValues,
|
||||
// This type is defined on library level and we can't change it
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends components.FieldValues | undefined = undefined,
|
||||
> = BaseFormProps<TFieldValues, TTransformedValues> &
|
||||
(FormPropsWithOptions<TFieldValues> | FormPropsWithParentForm<TFieldValues, TTransformedValues>)
|
||||
|
||||
/**
|
||||
* Base props for the Form component.
|
||||
*/
|
||||
interface BaseFormProps<
|
||||
TFieldValues extends components.FieldValues,
|
||||
// This type is defined on library level and we can't change it
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends components.FieldValues | undefined = undefined,
|
||||
> extends Omit<
|
||||
React.HTMLProps<HTMLFormElement>,
|
||||
'children' | 'className' | 'form' | 'onSubmit' | 'onSubmitCapture' | 'style'
|
||||
> {
|
||||
readonly className?: string | ((props: FormStateRenderProps<TFieldValues>) => string)
|
||||
readonly onSubmit: (
|
||||
values: TFieldValues,
|
||||
form: components.UseFormReturn<TFieldValues, TTransformedValues>
|
||||
) => unknown
|
||||
readonly style?:
|
||||
| React.CSSProperties
|
||||
| ((props: FormStateRenderProps<TFieldValues>) => React.CSSProperties)
|
||||
readonly children:
|
||||
| React.ReactNode
|
||||
| ((props: FormStateRenderProps<TFieldValues>) => React.ReactNode)
|
||||
readonly formRef?: React.MutableRefObject<
|
||||
components.UseFormReturn<TFieldValues, TTransformedValues>
|
||||
>
|
||||
|
||||
readonly onSubmitFailed?: (error: unknown) => Promise<void> | void
|
||||
readonly onSubmitSuccess?: () => Promise<void> | void
|
||||
readonly onSubmitted?: () => Promise<void> | void
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the Form component with parent form
|
||||
* or if form is passed as a prop.
|
||||
*/
|
||||
interface FormPropsWithParentForm<
|
||||
TFieldValues extends components.FieldValues,
|
||||
// This type is defined on library level and we can't change it
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
TTransformedValues extends components.FieldValues | undefined = undefined,
|
||||
> {
|
||||
readonly form: components.UseFormReturn<TFieldValues, TTransformedValues>
|
||||
readonly schema?: never
|
||||
readonly formOptions?: never
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the Form component with schema and form options.
|
||||
* Creates a new form instance. This is the default way to use the form.
|
||||
*/
|
||||
interface FormPropsWithOptions<TFieldValues extends components.FieldValues> {
|
||||
readonly form?: never
|
||||
readonly schema?: z.ZodObject<TFieldValues>
|
||||
readonly formOptions: Omit<components.UseFormProps<TFieldValues>, 'resolver'>
|
||||
}
|
||||
|
||||
/**
|
||||
* Form Render Props.
|
||||
*/
|
||||
export interface FormStateRenderProps<TFieldValues extends components.FieldValues> {
|
||||
/**
|
||||
* The form state. Contains the current values of the form fields.
|
||||
*/
|
||||
readonly formState: components.FormState<TFieldValues>
|
||||
/**
|
||||
* The form register function.
|
||||
* Adds a field to the form state.
|
||||
*/
|
||||
readonly register: reactHookForm.UseFormRegister<TFieldValues>
|
||||
/**
|
||||
* The form unregister function.
|
||||
* Removes a field from the form state.
|
||||
*/
|
||||
readonly unregister: reactHookForm.UseFormUnregister<TFieldValues>
|
||||
}
|
@ -6,3 +6,4 @@ export * from './Button/Button'
|
||||
export * from './Tooltip/Tooltip'
|
||||
export * from './Dialog'
|
||||
export * from './Alert'
|
||||
export * from './Form'
|
||||
|
@ -124,15 +124,18 @@ function RadioGroup(props: aria.RadioGroupProps, ref: React.ForwardedRef<HTMLDiv
|
||||
|
||||
const renderProps = useRenderProps({
|
||||
...props,
|
||||
defaultClassName: 'react-aria-RadioGroup',
|
||||
values: {
|
||||
orientation: props.orientation || 'vertical',
|
||||
isInvalid: state.isInvalid,
|
||||
isDisabled: state.isDisabled,
|
||||
isReadOnly: state.isReadOnly,
|
||||
isRequired: state.isRequired,
|
||||
isInvalid: state.isInvalid,
|
||||
state,
|
||||
},
|
||||
defaultClassName: 'react-aria-RadioGroup',
|
||||
defaultStyle: {},
|
||||
defaultChildren: null,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -577,6 +577,7 @@
|
||||
"tryAgain": "Try again",
|
||||
"arbitraryErrorTitle": "An error occurred",
|
||||
"arbitraryErrorSubtitle": "Please try again or contact the administrators.",
|
||||
"arbitraryFormErrorMessage": "Something went wrong while submitting the form. Please try again or contact the administrators.",
|
||||
|
||||
"subscribeSubmit": "Subscribe",
|
||||
"BankCardLabel": "Bank Card",
|
||||
|
5851
package-lock.json
generated
5851
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user