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/create-vitnode-app/copy-of-vitnode-plugin/root/.swcrc b/packages/create-vitnode-app/copy-of-vitnode-plugin/root/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-plugin/root/.swcrc +++ b/packages/create-vitnode-app/copy-of-vitnode-plugin/root/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/create-vitnode-app/copy-of-vitnode-plugin/root/tsconfig.build.json b/packages/create-vitnode-app/copy-of-vitnode-plugin/root/tsconfig.build.json new file mode 100644 index 000000000..997f1529d --- /dev/null +++ b/packages/create-vitnode-app/copy-of-vitnode-plugin/root/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/create-vitnode-app/package.json b/packages/create-vitnode-app/package.json index e75459a0b..b25c161d5 100644 --- a/packages/create-vitnode-app/package.json +++ b/packages/create-vitnode-app/package.json @@ -22,7 +22,7 @@ "eslint-react" ], "scripts": { - "build:scripts": "tsc && node dist/src/prepare/prepare.js", + "build:scripts": "tsc -p tsconfig.build.json && node dist/src/prepare/prepare.js", "start:scripts": "node dist/src/index.js", "lint": "eslint .", "lint:fix": "eslint . --fix" diff --git a/packages/create-vitnode-app/src/plugin/create/create-package-json.ts b/packages/create-vitnode-app/src/plugin/create/create-package-json.ts index c08f862f3..cb9e9d6ed 100644 --- a/packages/create-vitnode-app/src/plugin/create/create-package-json.ts +++ b/packages/create-vitnode-app/src/plugin/create/create-package-json.ts @@ -27,8 +27,8 @@ export const createPluginPackageJSON = async ({ type: "module", scripts: { "build:plugins": - "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - dev: 'concurrently "tsc -w --preserveWatchOutput" "swc src -d dist --config-file .swcrc -w" "tsc-alias -w" "vitnode plugin --w"', + "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + dev: 'concurrently "tsc -w -p tsconfig.build.json --preserveWatchOutput" "swc src -d dist --config-file .swcrc -w" "tsc-alias -w -p tsconfig.build.json" "vitnode plugin --w"', "dev:email": "email dev --dir src/emails", ...withIf(eslint, { lint: "turbo lint", diff --git a/packages/create-vitnode-app/tsconfig.build.json b/packages/create-vitnode-app/tsconfig.build.json new file mode 100644 index 000000000..e15e9be99 --- /dev/null +++ b/packages/create-vitnode-app/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/node-cron/.swcrc b/packages/node-cron/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/node-cron/.swcrc +++ b/packages/node-cron/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/node-cron/package.json b/packages/node-cron/package.json index 973a400cb..625bf1f0b 100644 --- a/packages/node-cron/package.json +++ b/packages/node-cron/package.json @@ -25,8 +25,8 @@ } }, "scripts": { - "build:plugins": "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - "dev:plugins": "concurrently \"tsc -w --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w\"", + "build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + "dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"", "lint": "eslint .", "lint:fix": "eslint . --fix" }, diff --git a/packages/node-cron/tsconfig.build.json b/packages/node-cron/tsconfig.build.json new file mode 100644 index 000000000..997f1529d --- /dev/null +++ b/packages/node-cron/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/nodemailer/.swcrc b/packages/nodemailer/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/nodemailer/.swcrc +++ b/packages/nodemailer/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/nodemailer/package.json b/packages/nodemailer/package.json index 485b77ab5..99a0ec7b3 100644 --- a/packages/nodemailer/package.json +++ b/packages/nodemailer/package.json @@ -24,8 +24,8 @@ } }, "scripts": { - "build:plugins": "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - "dev:plugins": "concurrently \"tsc -w --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w\"", + "build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + "dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"", "lint": "eslint .", "lint:fix": "eslint . --fix" }, diff --git a/packages/nodemailer/tsconfig.build.json b/packages/nodemailer/tsconfig.build.json new file mode 100644 index 000000000..997f1529d --- /dev/null +++ b/packages/nodemailer/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/resend/.swcrc b/packages/resend/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/resend/.swcrc +++ b/packages/resend/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/resend/package.json b/packages/resend/package.json index 751f6cb2a..9f58e9061 100644 --- a/packages/resend/package.json +++ b/packages/resend/package.json @@ -23,8 +23,8 @@ } }, "scripts": { - "build:plugins": "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - "dev:plugins": "concurrently \"tsc -w --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w\"", + "build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + "dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"", "lint": "eslint .", "lint:fix": "eslint . --fix" }, diff --git a/packages/resend/tsconfig.build.json b/packages/resend/tsconfig.build.json new file mode 100644 index 000000000..997f1529d --- /dev/null +++ b/packages/resend/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/s3/.swcrc b/packages/s3/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/s3/.swcrc +++ b/packages/s3/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/s3/package.json b/packages/s3/package.json index 547d26574..19db36a0f 100644 --- a/packages/s3/package.json +++ b/packages/s3/package.json @@ -25,8 +25,8 @@ } }, "scripts": { - "build:plugins": "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - "dev:plugins": "concurrently \"tsc -w --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w\"", + "build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + "dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"", "lint": "eslint .", "lint:fix": "eslint . --fix" }, diff --git a/packages/s3/tsconfig.build.json b/packages/s3/tsconfig.build.json new file mode 100644 index 000000000..997f1529d --- /dev/null +++ b/packages/s3/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/supabase-storage/.swcrc b/packages/supabase-storage/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/supabase-storage/.swcrc +++ b/packages/supabase-storage/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/supabase-storage/package.json b/packages/supabase-storage/package.json index 520ab2b59..0cb93a69c 100644 --- a/packages/supabase-storage/package.json +++ b/packages/supabase-storage/package.json @@ -24,8 +24,8 @@ } }, "scripts": { - "build:plugins": "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - "dev:plugins": "concurrently \"tsc -w --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w\"", + "build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + "dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"", "lint": "eslint .", "lint:fix": "eslint . --fix" }, diff --git a/packages/supabase-storage/tsconfig.build.json b/packages/supabase-storage/tsconfig.build.json new file mode 100644 index 000000000..997f1529d --- /dev/null +++ b/packages/supabase-storage/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/vitnode/.swcrc b/packages/vitnode/.swcrc index eba97079c..8f099dc7a 100644 --- a/packages/vitnode/.swcrc +++ b/packages/vitnode/.swcrc @@ -1,5 +1,6 @@ { "$schema": "https://swc.rs/schema.json", + "exclude": ["\\.test\\.tsx?$"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/vitnode/package.json b/packages/vitnode/package.json index 0c09772fb..95dcddb18 100644 --- a/packages/vitnode/package.json +++ b/packages/vitnode/package.json @@ -98,8 +98,8 @@ "scripts": { "build:scripts": "tsup", "start:scripts": "node dist/scripts/scripts.js init", - "build:plugins": "tsc && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.json", - "dev": "concurrently \"tsc -w --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w\" \"node dist/scripts/scripts.js plugin --w\"", + "build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json", + "dev": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\" \"node dist/scripts/scripts.js plugin --w\"", "dev:email": "email dev --dir src/emails", "test": "vitest run", "test:watch": "vitest", 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; + }) => ( +