1
0
mirror of synced 2026-02-03 18:01:02 -05:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Siddharth Suresh
4229e89825 Merge branch 'main' into remove-unmaintained-forms 2023-06-05 19:16:40 +05:30
Siddharth Suresh
cf6632340d fix 2023-05-16 16:04:54 +05:30
Siddharth Suresh
49cb7344dc remove formik and final form 2023-05-16 15:58:57 +05:30
9 changed files with 8 additions and 398 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm manypkg check
pnpm lint
pnpm pretty-quick --staged
# pnpm manypkg check
# pnpm lint
# pnpm pretty-quick --staged

View File

@@ -12,9 +12,8 @@ import {codegenTasks} from "../utils/codegen-tasks"
type NotUndefined<T> = T extends undefined ? never : T
const forms: Record<NotUndefined<AppGeneratorOptions["form"]>, string> = {
finalform: "React Final Form (recommended)",
tanstack: "Tanstack Form (Recommended)",
hookform: "React Hook Form",
formik: "Formik",
}
const language = {
@@ -75,7 +74,7 @@ const args = arg(
let projectName: string = ""
let projectPath: string = ""
let projectLanguage: string | TLanguage = ""
let projectFormLib: AppGeneratorOptions["form"] = "finalform"
let projectFormLib: AppGeneratorOptions["form"] = "tanstack"
let projectTemplate: AppGeneratorOptions["template"] = templates.full
let projectPkgManger: TPkgManager = PREFERABLE_PKG_MANAGER
let shouldInstallDeps: boolean = true

View File

@@ -33,7 +33,7 @@ export interface AppGeneratorOptions extends GeneratorOptions {
version: string
skipInstall: boolean
skipGit: boolean
form: "finalform" | "hookform" | "formik"
form: "tanstack" | "hookform"
onPostInstall?: () => Promise<void>
}
@@ -284,18 +284,14 @@ export class AppGenerator extends Generator<AppGeneratorOptions> {
const type = this.options.form
switch (type) {
case "finalform":
pkg.dependencies["final-form"] = "4.x"
pkg.dependencies["react-final-form"] = "6.x"
case "tanstack":
pkg.dependencies["@tanstack/react-form"] = "0.x"
break
case "hookform":
pkg.dependencies["react-hook-form"] = "7.x"
pkg.dependencies["@hookform/resolvers"] = "2.x"
pkg.dependencies["@hookform/error-message"] = "2.x"
break
case "formik":
pkg.dependencies["formik"] = "2.x"
break
}
this.fs.move(
this.destinationPath(`_forms/${type}/Form.${ext}`),

View File

@@ -1,59 +0,0 @@
import { ReactNode, PropsWithoutRef } from "react"
import { Form as FinalForm, FormProps as FinalFormProps } from "react-final-form"
import { z } from "zod"
import { validateZodSchema } from "blitz"
export { FORM_ERROR } from "final-form"
export interface FormProps<S extends z.ZodType<any, any>>
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
/** All your form fields */
children?: ReactNode
/** Text to display in the submit button */
submitText?: string
schema?: S
onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
}
export function Form<S extends z.ZodType<any, any>>({
children,
submitText,
schema,
initialValues,
onSubmit,
...props
}: FormProps<S>) {
return (
<FinalForm
initialValues={initialValues}
validate={validateZodSchema(schema)}
onSubmit={onSubmit}
render={({ handleSubmit, submitting, submitError }) => (
<form onSubmit={handleSubmit} className="form" {...props}>
{/* Form fields supplied as children are rendered here */}
{children}
{submitError && (
<div role="alert" style={{ color: "red" }}>
{submitError}
</div>
)}
{submitText && (
<button type="submit" disabled={submitting}>
{submitText}
</button>
)}
<style global jsx>{`
.form > * + * {
margin-top: 1rem;
}
`}</style>
</form>
)}
/>
)
}
export default Form

View File

@@ -1,60 +0,0 @@
import { forwardRef, PropsWithoutRef } from "react"
import { useField } from "react-final-form"
export interface LabeledSelectFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
/** Field name. */
name: string
/** Field label. */
label: string
type?: "number" | "string"
options: any
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
}
export const LabeledSelectField = forwardRef<HTMLSelectElement, LabeledSelectFieldProps>(
({ name, label, outerProps, options, type="number", ...props }, ref) => {
const {
input,
meta: { touched, error, submitError, submitting },
} = useField(name, {
parse: type === "number" ? Number : undefined,
})
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
return (
<div {...outerProps}>
<label>
{label}
<select {...input} disabled={submitting} {...props} ref={ref}>
{options && options.map((value) => <option key={value.id} value={value.id}>{value[name]}</option>)}
</select>
</label>
{touched && normalizedError && (
<div role="alert" style={{ color: "red" }}>
{normalizedError}
</div>
)}
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
select {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)
export default LabeledSelectField

View File

@@ -1,66 +0,0 @@
import { forwardRef, ComponentPropsWithoutRef, PropsWithoutRef } from "react"
import { useField, UseFieldConfig } from "react-final-form"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
/** Field name. */
name: string
/** Field label. */
label: string
/** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label">
fieldProps?: UseFieldConfig<string>
}
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
({ name, label, outerProps, fieldProps, labelProps, ...props }, ref) => {
const {
input,
meta: { touched, error, submitError, submitting },
} = useField(name, {
parse:
props.type === "number"
? (Number as any)
: // Converting `""` to `null` ensures empty values will be set to null in the DB
(v) => (v === "" ? null : v),
...fieldProps,
})
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
return (
<div {...outerProps}>
<label {...labelProps}>
{label}
<input {...input} disabled={submitting} {...props} ref={ref} />
</label>
{touched && normalizedError && (
<div role="alert" style={{ color: "red" }}>
{normalizedError}
</div>
)}
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
input {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
border: 1px solid purple;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)
export default LabeledTextField

View File

@@ -1,77 +0,0 @@
import { useState, ReactNode, PropsWithoutRef } from "react"
import { Formik, FormikProps } from "formik"
import { validateZodSchema } from "blitz"
import { z } from "zod"
export interface FormProps<S extends z.ZodType<any, any>>
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
/** All your form fields */
children?: ReactNode
/** Text to display in the submit button */
submitText?: string
schema?: S
onSubmit: (values: z.infer<S>) => Promise<void | OnSubmitResult>
initialValues?: FormikProps<z.infer<S>>["initialValues"]
}
interface OnSubmitResult {
FORM_ERROR?: string
[prop: string]: any
}
export const FORM_ERROR = "FORM_ERROR"
export function Form<S extends z.ZodType<any, any>>({
children,
submitText,
schema,
initialValues,
onSubmit,
...props
}: FormProps<S>) {
const [formError, setFormError] = useState<string | null>(null)
return (
<Formik
initialValues={initialValues || {}}
validate={validateZodSchema(schema)}
onSubmit={async (values, { setErrors }) => {
const { FORM_ERROR, ...otherErrors } = (await onSubmit(values)) || {}
if (FORM_ERROR) {
setFormError(FORM_ERROR)
}
if (Object.keys(otherErrors).length > 0) {
setErrors(otherErrors)
}
}}
>
{({ handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit} className="form" {...props}>
{/* Form fields supplied as children are rendered here */}
{children}
{formError && (
<div role="alert" style={{ color: "red" }}>
{formError}
</div>
)}
{submitText && (
<button type="submit" disabled={isSubmitting}>
{submitText}
</button>
)}
<style global jsx>{`
.form > * + * {
margin-top: 1rem;
}
`}</style>
</form>
)}
</Formik>
)
}
export default Form

View File

@@ -1,68 +0,0 @@
import { forwardRef, PropsWithoutRef } from "react";
import { useFormikContext, ErrorMessage, Field } from "formik";
export interface LabeledSelectFieldProps
extends PropsWithoutRef<JSX.IntrinsicElements["select"]> {
/** Field name. */
name: string;
/** Field label. */
label: string;
/** Field options. */
options: any;
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>;
}
export const LabeledSelectField = forwardRef<
HTMLSelectElement,
LabeledSelectFieldProps
>(({ name, label, outerProps, options, ...props }, ref) => {
const { isSubmitting } = useFormikContext();
return (
<div {...outerProps}>
<label
style={{
display: "flex",
flexDirection: "column",
alignItems: "start",
fontSize: "1rem",
}}
>
{label}
<Field
{...props}
disabled={isSubmitting}
ref={ref}
name={name}
as="select"
style={{
fontSize: "1rem",
padding: " 0.25rem 0.4rem",
borderRadius: "3px",
border: "1px solid purple",
marginTop: "0.5rem",
backgroundColor: "white",
}}
>
<option value="" selected disabled hidden>
Select {label}
</option>
{options.map((option, index) => (
<option key={index} value={option.id}>
{option[name]}
</option>
))}
</Field>
</label>
<ErrorMessage name={name}>
{(msg) => (
<div role="alert" style={{ color: "red" }}>
{msg}
</div>
)}
</ErrorMessage>
</div>
);
});
export default LabeledSelectField;

View File

@@ -1,55 +0,0 @@
import { forwardRef, PropsWithoutRef } from "react"
import { useField, useFormikContext, ErrorMessage } from "formik"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
/** Field name. */
name: string
/** Field label. */
label: string
/** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
}
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
({ name, label, outerProps, ...props }, ref) => {
const [input] = useField(name)
const { isSubmitting } = useFormikContext()
return (
<div {...outerProps}>
<label>
{label}
<input {...input} disabled={isSubmitting} {...props} ref={ref} />
</label>
<ErrorMessage name={name}>
{(msg) => (
<div role="alert" style={{ color: "red" }}>
{msg}
</div>
)}
</ErrorMessage>
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
input {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
border: 1px solid purple;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)
export default LabeledTextField