0.1.33 • Published 15 days ago

ozef v0.1.33

Weekly downloads
-
License
MIT
Repository
github
Last release
15 days ago

Opinionated Zod-empowered forms

Introduction

Ozef is an opinionated library that aims to guarantee type-safe, declarative forms with minimal boilerplate. It is built on top of Zod which provides a powerful type system for validating data.

Features Ozef supports:

  • Guaranteed type-safety in onSubmit
  • async onSubmit
  • Declarative forms
  • Validation checking
  • Input types like radio and select

Ozef is not a component library. It does not provide any pre-built components. Instead, plug in existing components to build forms.

Ozef lets you build forms like:

import Input from "./CustomInput";

const NewFlowForm = ozef({
  schema: z.object({
    name: z.string().min(3),
    email: z.string().email(),
    // for radios
    favoriteColor: z.enum(["Red", "Blue"]),
    // for selects
    favoriteColor: z.union([z.literal("Red"), z.literal("Blue")]),
  }),
  // Plug in your own input components
  Input,
  // Define components for each type
  InputRadio: ({ radioValue, ...props }) => (
    <div>
      {radioValue}
      <input type="radio" {...props} />
    </div>
  ),
  // Error labels
  Error: ({ error }) => <span className="text-red-500">{error}</span>,
  Submit: ({ submitting }) => (
    <RoundedButton type="submit" loading={submitting}>
      Submit
    </RoundedButton>
  ),
});

<NewFlowForm
  className="flex flex-col gap-2"
  onSubmit={async (vals) => {
    /** vals has the type { name: string, email: string, favoriteColor: "Red" | "Blue" } */
    ...
  }}
>
  <NewFlowForm.Field.Name prefixIcon="icon_1" />
  <NewFlowForm.Error.Name />

  <NewFlowForm.Field.Email />
  <NewFlowForm.Error.Email />

  <NewFlowForm.Error.FavoriteColor />
  <NewFlowForm.Field.FavoriteColor>
    <NewFlowForm.Field.FavoriteColor.Blue />
    <NewFlowForm.Field.FavoriteColor.Red />
  </NewFlowForm.Field.FavoriteColor>

  <NewFlowForm.Event.Submit />
  <NewFlowForm.Error.Submission error="Please fill out the form" />
</NewFlowForm>

with full type-script support!

Installation

Ozef has minimal dependencies (just Zod and Jotai) and is easy to install.

npm i zod@3.21.4 jotai ozef

Usage

Basic usage

import ozef from "ozef";

const Form = ozef({
  schema: z.object({
    name: z.string().min(3),
    email: z.string().email(),
  }),
});

const SomeComponent = () => {
  return (
    <Form
      onSubmit={async (vals) => {
        // vals is guaranteed to be of type { name: string, email: string }
        ...
      }}
    >
      // Use `Field` components to render inputs for the form
      <Form.Field.Name />
      <Form.Field.Email />

      // Use `Error` components to render error labels
      <Form.Error.Name />

      // Use `Event` components to render special user events components
      <Form.Event.Submit />
    </Form>
  );
};

Ozef Input Components

Components need to modified before being able to be used with Ozef. This is because Ozef needs to be able to pass certain props to the components.

import { type OzefInputProps } from "ozef";

type InputProps = OzefInputProps & {
  // Add your own props
  prefixIcon?: string;
};

const Input = ({ prefixIcon, hasError, ...props }: InputProps) => {
  return (
    <div
      className={`${
        props.className
      } ${hasError ? "focus-within:ring-red-500" : ""}`}
    >
      {prefixIcon && (
        <MaterialsIcon className="!text-xl text-zinc-500" icon={prefixIcon} />
      )}
      <input
        {/* This is the important part. Ozef needs to pass props to the native input component. */}
        {...props}
        className="..."
      />
    </div>
  );
};

Defaults

import ozef from "ozef";

const Form = ozef({
  schema: z.object({
    name: z.string().min(3),
    email: z.string().email(),
  }),
  defaults: {
    name: "John Doe",
    email: "john-doe@gmail.com"
  }
});

const SomeComponent = () => {
  return (
    <Form
      onSubmit={async (vals) => {
        // vals is guaranteed to be of type { name: string, email: string }
        ...
      }}
    >
      // Use `Field` components to render inputs for the form
      <Form.Field.Name />
      <Form.Field.Email />

      // Use `Error` components to render error labels
      <Form.Error.Name />

      // Use `Event` components to render special user events components
      <Form.Event.Submit />
    </Form>
  );
};

Variable defaults

If you're passing down a variable as defaults, you'll need to use useMemo to prevent the form from re-rendering every time the parent component re-renders.

import ozef from "ozef";
import { useMemo } from "react";

const SomeComponent = ({defaults}: Props) => {
  const Form = useMemo(() => ozef({
    schema: z.object({
      name: z.string().min(3),
      email: z.string().email(),
    }),
    defaults
  }), [defaults])

  return (
    <Form
      onSubmit={async (vals) => {
        // vals is guaranteed to be of type { name: string, email: string }
        ...
      }}
    >
      // Use `Field` components to render inputs for the form
      <Form.Field.Name />
      <Form.Field.Email />

      // Use `Error` components to render error labels
      <Form.Error.Name />

      // Use `Event` components to render special user events components
      <Form.Event.Submit />
    </Form>
  );
};

shadcn/Radix Select example

import ozef from "ozef";

const UpdateRewriteSettingsForm = ozef({
  schema: z.object({
    terseness: z.enum(["short", "medium", "long"]),
  }),
});

const ShadcnExample = () => {
  // getter hook
  const terseness = UpdateRewriteSettingsForm.Field.Terseness.useValue();

  return (
    <UpdateRewriteSettingsForm
      onSubmit={() => {
        // handler
      }}
    >
      <Select
        value={terseness}
        // setter function
        onValueChange={UpdateRewriteSettingsForm.Field.Terseness.setValue}
      >
        <div className="space-y-1">
          <Label>Terseness</Label>
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <UpdateRewriteSettingsForm.Field.Terseness.Short />
            <UpdateRewriteSettingsForm.Field.Terseness.Medium />
            <UpdateRewriteSettingsForm.Field.Terseness.Long />
          </SelectContent>
        </div>
      </Select>
    </UpdateRewriteSettingsForm>
  );
};
0.1.33

15 days ago

0.1.32

5 months ago

0.1.30

5 months ago

0.1.31

5 months ago

0.1.29

5 months ago

0.1.13

7 months ago

0.1.14

7 months ago

0.1.15

7 months ago

0.1.27

6 months ago

0.1.28

6 months ago

0.1.20

7 months ago

0.1.21

6 months ago

0.1.22

6 months ago

0.1.23

6 months ago

0.1.24

6 months ago

0.1.25

6 months ago

0.1.26

6 months ago

0.1.16

7 months ago

0.1.17

7 months ago

0.1.18

7 months ago

0.1.19

7 months ago

0.1.12

8 months ago

0.1.11

8 months ago

0.1.10

8 months ago

0.1.9

8 months ago

0.1.8

8 months ago

0.1.7

8 months ago

0.1.6

8 months ago

0.1.5

8 months ago

0.1.4

8 months ago

0.1.3

8 months ago

0.1.1

8 months ago

0.1.0

8 months ago