From 105e9517ed5ff4cb82beeb5635ed9db99a6a6a41 Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Tue, 7 Jul 2026 21:35:10 +0200 Subject: [PATCH 1/2] feat(form): Add editor to auto form --- apps/docs/content/docs/ui/auto-form.mdx | 15 +++ apps/docs/content/docs/ui/editor.mdx | 112 ++++++++++++++++ apps/docs/content/docs/ui/meta.json | 1 + apps/docs/src/examples/auto-form.tsx | 15 +++ apps/docs/src/examples/editor.tsx | 32 +++++ .../components/form/fields/editor.test.tsx | 113 ++++++++++++++++ .../src/components/form/fields/editor.tsx | 43 ++++++ .../form/fields/radio-group.test.tsx | 126 ++++++++++++++++++ .../components/form/fields/radio-group.tsx | 2 +- .../components/form/fields/select.test.tsx | 120 +++++++++++++++++ .../src/components/form/fields/select.tsx | 2 +- .../vitnode/src/components/ui/editor.test.tsx | 46 +++++++ packages/vitnode/src/components/ui/editor.tsx | 21 ++- .../src/views/admin/views/core/test.tsx | 9 ++ 14 files changed, 650 insertions(+), 7 deletions(-) create mode 100644 apps/docs/content/docs/ui/editor.mdx create mode 100644 apps/docs/src/examples/editor.tsx create mode 100644 packages/vitnode/src/components/form/fields/editor.test.tsx create mode 100644 packages/vitnode/src/components/form/fields/editor.tsx create mode 100644 packages/vitnode/src/components/form/fields/radio-group.test.tsx create mode 100644 packages/vitnode/src/components/form/fields/select.test.tsx create mode 100644 packages/vitnode/src/components/ui/editor.test.tsx diff --git a/apps/docs/content/docs/ui/auto-form.mdx b/apps/docs/content/docs/ui/auto-form.mdx index 7e5b2e761..55dd75627 100644 --- a/apps/docs/content/docs/ui/auto-form.mdx +++ b/apps/docs/content/docs/ui/auto-form.mdx @@ -12,6 +12,7 @@ description: Component creates form based on Zod schemas & react-hook-form with ```tsx import { AutoForm } from "@vitnode/core/components/form/auto-form"; import { AutoFormCheckbox } from "@vitnode/core/components/form/fields/checkbox"; +import { AutoFormEditor } from "@vitnode/core/components/form/fields/editor"; import { AutoFormInput } from "@vitnode/core/components/form/fields/input"; import { AutoFormSelect } from "@vitnode/core/components/form/fields/select"; import { AutoFormTextarea } from "@vitnode/core/components/form/fields/textarea"; @@ -40,6 +41,10 @@ const formSchema = z.object({ message: "You must accept the terms and conditions", }), description: z.string().min(10, "Description must be at least 10 characters"), + content: z + .string() + .min(1, "Content is required") + .default("

Write your content here...

"), search: z.string().optional(), }); ``` @@ -119,6 +124,16 @@ const formSchema = z.object({ /> ), }, + { + id: "content", + component: props => ( + + ), + }, { id: "search", component: props => ( diff --git a/apps/docs/content/docs/ui/editor.mdx b/apps/docs/content/docs/ui/editor.mdx new file mode 100644 index 000000000..2d954c5de --- /dev/null +++ b/apps/docs/content/docs/ui/editor.mdx @@ -0,0 +1,112 @@ +--- +title: Editor +description: Rich text editor built on TipTap for editing and rendering HTML content. +--- + +## Preview + + + +## Usage + +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; + + + + +```ts +import { z } from "zod"; +import { AutoForm } from "@vitnode/core/components/form/auto-form"; +import { AutoFormEditor } from "@vitnode/core/components/form/fields/editor"; +``` + +```ts +const formSchema = z.object({ + content: z + .string() + .min(1, "Content is required") + .default("

Write your content here...

"), +}); +``` + +```tsx + ( + + ), + }, + ]} +/> +``` + +
+ + + +```ts +import { Editor } from "@vitnode/core/components/ui/editor"; +``` + +The `Editor` is uncontrolled by default — pass `value` as the initial HTML and +listen for changes with `onChange`, which receives the current HTML string: + +```tsx +const [content, setContent] = useState("

Hello World! 🌎️

"); + +; +``` + +
+
+ +## Rendering Content + +Use `EditorContent` to render the stored HTML as read-only content outside of +the editor (e.g. on a public page): + +```ts +import { EditorContent } from "@vitnode/core/components/ui/editor-content"; +``` + +```tsx + +``` + +## Props + +import { TypeTable } from "fumadocs-ui/components/type-table"; + + void", + default: "", + }, + disableScroll: { + description: + "Disables the internal max-height scroll area so the editor grows with its content.", + type: "boolean", + default: "false", + }, + className: { + description: "Additional classes applied to the editor wrapper.", + type: "string", + default: "", + }, + }} +/> diff --git a/apps/docs/content/docs/ui/meta.json b/apps/docs/content/docs/ui/meta.json index 8fc96c8b4..288445241 100644 --- a/apps/docs/content/docs/ui/meta.json +++ b/apps/docs/content/docs/ui/meta.json @@ -16,6 +16,7 @@ "auto-form", "checkbox", "combobox", + "editor", "input", "input-group", "radio-group", diff --git a/apps/docs/src/examples/auto-form.tsx b/apps/docs/src/examples/auto-form.tsx index 3a8993858..685901a54 100644 --- a/apps/docs/src/examples/auto-form.tsx +++ b/apps/docs/src/examples/auto-form.tsx @@ -2,6 +2,7 @@ import { AutoForm } from "@vitnode/core/components/form/auto-form"; import { AutoFormCheckbox } from "@vitnode/core/components/form/fields/checkbox"; +import { AutoFormEditor } from "@vitnode/core/components/form/fields/editor"; import { AutoFormInput } from "@vitnode/core/components/form/fields/input"; import { AutoFormSelect } from "@vitnode/core/components/form/fields/select"; import { AutoFormTextarea } from "@vitnode/core/components/form/fields/textarea"; @@ -22,6 +23,10 @@ export default function AutoFormExample() { description: z .string() .min(10, "Description must be at least 10 characters"), + content: z + .string() + .min(1, "Content is required") + .default("

Write your content here...

"), search: z.string().optional(), }); @@ -79,6 +84,16 @@ export default function AutoFormExample() { /> ), }, + { + id: "content", + component: props => ( + + ), + }, { id: "search", component: props => ( diff --git a/apps/docs/src/examples/editor.tsx b/apps/docs/src/examples/editor.tsx new file mode 100644 index 000000000..1c9242e8c --- /dev/null +++ b/apps/docs/src/examples/editor.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { AutoForm } from "@vitnode/core/components/form/auto-form"; +import { AutoFormEditor } from "@vitnode/core/components/form/fields/editor"; +import { z } from "zod"; + +export default function EditorExample() { + const formSchema = z.object({ + content: z + .string() + .min(1, "Content is required") + .default("

Write your content here...

"), + }); + + return ( + ( + + ), + }, + ]} + formSchema={formSchema} + /> + ); +} diff --git a/packages/vitnode/src/components/form/fields/editor.test.tsx b/packages/vitnode/src/components/form/fields/editor.test.tsx new file mode 100644 index 000000000..dcd0bcb55 --- /dev/null +++ b/packages/vitnode/src/components/form/fields/editor.test.tsx @@ -0,0 +1,113 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { type FieldValues, useForm } from "react-hook-form"; +import { describe, expect, it, vi } from "vitest"; + +import { Form, FormField } from "@/components/ui/form"; + +import { AutoFormEditor } from "./editor"; + +vi.mock("next-intl", () => ({ + useLocale: () => "en", + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/components/ui/editor", () => ({ + Editor: ({ + value, + onChange, + onBlur, + ...props + }: { + onBlur?: () => void; + onChange?: (value: string) => void; + value?: string; + }) => ( +