vantezzen / auto-form

🌟 A React component that automatically creates a @shadcn/ui form based on a zod schema.
https://vantezzen.github.io/auto-form/
2.27k stars 81 forks source link

String Arrays Support #35

Open bidrang opened 9 months ago

bidrang commented 9 months ago

Hi there, First of all thanks for the great tool which you developed. For my case, I need AutoForm to support String Arrays. which unfortunately, I couldn't figure out how to do it.

The field is as below:

highlights: z
      .array(
        z
          .string()
          .describe(
            "e.g. Increased profits by 20% from 2011-2012 through viral advertising"
          )
      )
      .describe("Specify multiple accomplishments")
      .optional(),
vantezzen commented 8 months ago

Currently, AutoForm only supports array of objects (https://github.com/vantezzen/auto-form#arrays) as inferring field labels etc. needs to be done another way otherwise. PRs welcome if you want to extend the functionality there!

bidrang commented 8 months ago

Thanks Bennett for your response. I've resolved my issue by creating a new INPUT_COMPONENTS and tweaking the AutoForm as needed. However, given the specific nature of my modifications and the number of custom changes involved, submitting a pull request might not be feasible.

salat-23 commented 8 months ago

Thanks Bennett for your response. I've resolved my issue by creating a new INPUT_COMPONENTS and tweaking the AutoForm as needed. However, given the specific nature of my modifications and the number of custom changes involved, submitting a pull request might not be feasible.

Hi, can you share the modifications? I have also stumbled upon this problem

bidrang commented 8 months ago

Hi, can you share the modifications? I have also stumbled upon this problem

For my case I created a Component for Tags listing for my project as blow:

import { useEffect, useRef, useState } from "react";
import { Button } from "./ui/button";
import { XIcon } from "lucide-react";

type Params = {
  onValueChange?: (value: string[]) => void;
  value: string[];
  readonly?: boolean;
};

export function TagsListInput({ value, onValueChange, readonly }: Params) {
  const [tags, setTags] = useState<string[]>();
  const inputRef = useRef(null);

  useEffect(() => {
    if (value) {
      setTags(value);
    }
  }, [value]);

  useEffect(() => {
    if (onValueChange && tags) {
      onValueChange(tags);
    }
  }, [tags]);

  return (
    <>
      {tags &&
        tags.map((item, index) => (
          <Button
            key={"tag-" + index + "item"}
            className="me-1 font-normal text-xs px-2.5 py-0.5 h-auto mb-1"
            variant="secondary"
            onClick={(e) => {
              let temp = tags.filter((str, idx) => idx != index);
              setTags(temp);
              e.preventDefault();
            }}
          >
            {item}
            <XIcon className="ms-2 w-3" />
          </Button>
        ))}
      <input
        type="text"
        placeholder="Enter Keyword"
        className="text-sm px-2.5 py-0.5 border-muted border w-32"
        ref={inputRef}
        onKeyDown={(e) => {
          if (e.key === "Enter" || e.key === "Tab") {
            let temp = tags;
            if (!temp) temp = [];
            if (inputRef.current) {
              temp.push((inputRef.current as HTMLInputElement).value);
              (inputRef.current as HTMLInputElement).value = "";
            }
            setTags([...temp]);
            e.preventDefault();
          }
        }}
      />
    </>
  );
}

And I added an Autoform Field to the Autoform.ts file to support this new field as below:

function AutoFormTagsInput({
  label,
  isRequired,
  field,
  fieldConfigItem,
  fieldProps,
}: AutoFormInputComponentProps) {
  return (
    <FormItem>
      <FormLabel>
        {label}
        {isRequired && <span className="text-destructive"> *</span>}
      </FormLabel>
      <FormControl>
        <TagsListInput
          value={field.value}
          onValueChange={field.onChange}
          {...fieldProps}
        />
      </FormControl>
      {fieldConfigItem.description && (
        <FormDescription>{fieldConfigItem.description}</FormDescription>
      )}
      <FormMessage />
    </FormItem>
  );
}

I added the support for the new component to the INPUT_COMPONENTS type as below:

const INPUT_COMPONENTS = {
 ...
  tags: AutoFormTagsInput,
  textlist: AutoFormTextListInput,
};

And in AutoForm usage I set the FieldConfig as below:

fieldConfig={{
                keywords: { fieldType: "tags" },
              }}

BTW, as I noted Bennett has refactored the project a lot. So, you must figure out how to do this approach on the updated project structure.

I hope it helps.