nextworks 0.1.0-alpha.11 → 0.1.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +20 -9
  2. package/dist/kits/auth-core/.nextworks/docs/AUTH_CORE_README.md +3 -3
  3. package/dist/kits/auth-core/.nextworks/docs/AUTH_QUICKSTART.md +264 -244
  4. package/dist/kits/auth-core/app/(protected)/settings/profile/profile-form.tsx +120 -114
  5. package/dist/kits/auth-core/app/api/auth/forgot-password/route.ts +116 -114
  6. package/dist/kits/auth-core/app/api/auth/reset-password/route.ts +66 -63
  7. package/dist/kits/auth-core/app/api/auth/send-verify-email/route.ts +1 -1
  8. package/dist/kits/auth-core/app/api/users/[id]/route.ts +134 -127
  9. package/dist/kits/auth-core/app/auth/reset-password/page.tsx +186 -187
  10. package/dist/kits/auth-core/components/auth/dashboard.tsx +25 -2
  11. package/dist/kits/auth-core/components/auth/forgot-password-form.tsx +90 -90
  12. package/dist/kits/auth-core/components/auth/login-form.tsx +492 -467
  13. package/dist/kits/auth-core/components/auth/signup-form.tsx +28 -29
  14. package/dist/kits/auth-core/lib/auth.ts +46 -15
  15. package/dist/kits/auth-core/lib/forms/map-errors.ts +37 -11
  16. package/dist/kits/auth-core/lib/server/result.ts +45 -45
  17. package/dist/kits/auth-core/lib/validation/forms.ts +1 -2
  18. package/dist/kits/auth-core/package-deps.json +4 -2
  19. package/dist/kits/auth-core/types/next-auth.d.ts +1 -1
  20. package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +2 -8
  21. package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +18 -1
  22. package/dist/kits/blocks/app/templates/productlaunch/page.tsx +0 -2
  23. package/dist/kits/blocks/components/sections/FAQ.tsx +0 -1
  24. package/dist/kits/blocks/components/sections/Newsletter.tsx +2 -2
  25. package/dist/kits/blocks/components/ui/switch.tsx +78 -78
  26. package/dist/kits/blocks/components/ui/theme-selector.tsx +1 -1
  27. package/dist/kits/blocks/lib/themes.ts +1 -0
  28. package/dist/kits/blocks/package-deps.json +4 -4
  29. package/dist/kits/data/.nextworks/docs/DATA_QUICKSTART.md +128 -112
  30. package/dist/kits/data/.nextworks/docs/DATA_README.md +2 -1
  31. package/dist/kits/data/app/api/posts/[id]/route.ts +83 -83
  32. package/dist/kits/data/app/api/posts/route.ts +136 -138
  33. package/dist/kits/data/app/api/seed-demo/route.ts +1 -2
  34. package/dist/kits/data/app/api/users/[id]/route.ts +29 -17
  35. package/dist/kits/data/app/api/users/check-email/route.ts +1 -1
  36. package/dist/kits/data/app/api/users/check-unique/route.ts +30 -27
  37. package/dist/kits/data/app/api/users/route.ts +0 -2
  38. package/dist/kits/data/app/examples/demo/create-post-form.tsx +108 -106
  39. package/dist/kits/data/app/examples/demo/page.tsx +2 -1
  40. package/dist/kits/data/app/examples/demo/seed-demo-button.tsx +1 -1
  41. package/dist/kits/data/components/admin/posts-manager.tsx +727 -719
  42. package/dist/kits/data/components/admin/users-manager.tsx +435 -432
  43. package/dist/kits/data/lib/server/result.ts +5 -2
  44. package/dist/kits/data/package-deps.json +1 -1
  45. package/dist/kits/data/scripts/seed-demo.mjs +1 -2
  46. package/dist/kits/forms/app/api/wizard/route.ts +76 -71
  47. package/dist/kits/forms/app/examples/forms/server-action/page.tsx +78 -71
  48. package/dist/kits/forms/components/hooks/useCheckUnique.ts +85 -79
  49. package/dist/kits/forms/components/ui/form/form-control.tsx +28 -28
  50. package/dist/kits/forms/components/ui/form/form-description.tsx +23 -22
  51. package/dist/kits/forms/components/ui/form/form-item.tsx +21 -21
  52. package/dist/kits/forms/components/ui/form/form-label.tsx +24 -24
  53. package/dist/kits/forms/components/ui/form/form-message.tsx +28 -29
  54. package/dist/kits/forms/components/ui/switch.tsx +78 -78
  55. package/dist/kits/forms/lib/forms/map-errors.ts +1 -1
  56. package/dist/kits/forms/lib/validation/forms.ts +1 -2
  57. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import type { ZodError } from "zod";
3
- import type { Prisma } from "@prisma/client";
4
3
  import { zodErrorToFieldErrors, type FieldErrors } from "@/lib/api/errors";
5
4
 
6
5
  export type ApiResult<T = unknown> = {
@@ -65,7 +64,11 @@ function isPrismaKnownRequestError(
65
64
  function extractUniqueField(
66
65
  err: PrismaKnownRequestErrorLike,
67
66
  ): string | undefined {
68
- const target = (err.meta as any)?.target;
67
+ const meta = err.meta;
68
+ const target =
69
+ meta && typeof meta === "object" && "target" in meta
70
+ ? (meta as { target?: unknown }).target
71
+ : undefined;
69
72
  if (Array.isArray(target) && target.length) return String(target[0]);
70
73
  if (typeof target === "string") return String(target.split("_")[0]);
71
74
  return undefined;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "dependencies": {
3
- "@nextworks/blocks-core": "*",
3
+ "@nextworks/blocks-core": "0.1.0-alpha.14",
4
4
  "@prisma/client": "^6.16.1",
5
5
  "zod": "^3.23.8",
6
6
  "date-fns": "^4.1.0"
@@ -4,10 +4,9 @@ const prisma = new PrismaClient();
4
4
 
5
5
  async function main() {
6
6
  const adminEmail = process.env.SEED_ADMIN_EMAIL || "admin@example.com";
7
- const adminPassword = process.env.SEED_ADMIN_PASSWORD || "password123";
8
7
 
9
8
  // Upsert admin user
10
- const user = await prisma.user.upsert({
9
+ await prisma.user.upsert({
11
10
  where: { email: adminEmail },
12
11
  update: {},
13
12
  create: {
@@ -1,71 +1,76 @@
1
- import { NextRequest } from "next/server";
2
- import {
3
- jsonOk,
4
- jsonFromZod,
5
- jsonFromPrisma,
6
- jsonFail,
7
- } from "@/lib/server/result";
8
- import { wizardServerSchema } from "@/lib/validation/wizard";
9
- import { prisma } from "@/lib/prisma";
10
- import type { Prisma } from "@prisma/client";
11
- import { getServerSession } from "next-auth";
12
- import { authOptions } from "@/lib/auth";
13
-
14
- export const runtime = "nodejs";
15
-
16
- export async function POST(req: NextRequest) {
17
- try {
18
- const body = await req.json();
19
- const parsed = wizardServerSchema.parse(body);
20
- // coerce to typed server input
21
- const parsedTyped: import("@/lib/validation/wizard").WizardServerValues =
22
- parsed;
23
- // require session to attach author
24
- const session = await getServerSession(authOptions);
25
- if (!session?.user?.id)
26
- return jsonFail("Not authenticated", { status: 401 });
27
- const userId = session.user.id;
28
- // map visibility to published flag
29
- const published = parsed.visibility === "public";
30
- // create Post record (persist full set of fields)
31
- const data: Prisma.PostCreateInput = {
32
- title: parsed.title,
33
- slug: parsed.slug || undefined,
34
- content: parsed.content || undefined,
35
- excerpt: parsed.excerpt || undefined,
36
- tags: parsed.tags || undefined,
37
- published,
38
- author: { connect: { id: userId } },
39
- };
40
- const created = await prisma.post.create({ data });
41
- // return a cleaned, typed result object (maps published -> visibility)
42
- const result: import("@/lib/validation/wizard").WizardServerValues & {
43
- id: string;
44
- createdAt: Date;
45
- } = {
46
- title: created.title,
47
- slug: created.slug ?? "",
48
- content: created.content ?? "",
49
- excerpt: created.excerpt ?? "",
50
- visibility: created.published ? "public" : "private",
51
- tags: created.tags ?? "",
52
- id: created.id,
53
- createdAt: created.createdAt,
54
- };
55
- return jsonOk(result, { status: 201, message: "Created" });
56
- } catch (e: unknown) {
57
- // zod errors
58
- // prisma errors
59
- try {
60
- // detect zod error
61
- // @ts-ignore
62
- if (e?.issues) return jsonFromZod(e);
63
- } catch {}
64
- try {
65
- return jsonFromPrisma(e);
66
- } catch {
67
- console.error(e);
68
- return jsonOk(null, { message: "Unknown error" });
69
- }
70
- }
71
- }
1
+ import { NextRequest } from "next/server";
2
+ import {
3
+ jsonOk,
4
+ jsonFromZod,
5
+ jsonFromPrisma,
6
+ jsonFail,
7
+ } from "@/lib/server/result";
8
+ import { wizardServerSchema } from "@/lib/validation/wizard";
9
+ import { prisma } from "@/lib/prisma";
10
+ import { getServerSession } from "next-auth";
11
+ import { authOptions } from "@/lib/auth";
12
+
13
+ export const runtime = "nodejs";
14
+
15
+ export async function POST(req: NextRequest) {
16
+ try {
17
+ const body = await req.json();
18
+ const parsed = wizardServerSchema.parse(body);
19
+ // coerce to typed server input
20
+ void (parsed satisfies import("@/lib/validation/wizard").WizardServerValues);
21
+ // require session to attach author
22
+ const session = await getServerSession(authOptions);
23
+ if (!session?.user?.id)
24
+ return jsonFail("Not authenticated", { status: 401 });
25
+ const userId = session.user.id;
26
+ // map visibility to published flag
27
+ const published = parsed.visibility === "public";
28
+ // create Post record (persist full set of fields)
29
+ const data = {
30
+ title: parsed.title,
31
+ slug: parsed.slug || undefined,
32
+ content: parsed.content || undefined,
33
+ excerpt: parsed.excerpt || undefined,
34
+ tags: parsed.tags || undefined,
35
+ published,
36
+ author: { connect: { id: userId } },
37
+ } satisfies Parameters<typeof prisma.post.create>[0]["data"];
38
+ const created = await prisma.post.create({ data });
39
+ // return a cleaned, typed result object (maps published -> visibility)
40
+ const result: import("@/lib/validation/wizard").WizardServerValues & {
41
+ id: string;
42
+ createdAt: Date;
43
+ } = {
44
+ title: created.title,
45
+ slug: created.slug ?? "",
46
+ content: created.content ?? "",
47
+ excerpt: created.excerpt ?? "",
48
+ visibility: created.published ? "public" : "private",
49
+ tags: created.tags ?? "",
50
+ id: created.id,
51
+ createdAt: created.createdAt,
52
+ };
53
+ return jsonOk(result, { status: 201, message: "Created" });
54
+ } catch (e: unknown) {
55
+ // zod errors
56
+ // prisma errors
57
+ try {
58
+ // detect zod error (ZodError has an `issues` array)
59
+ if (
60
+ typeof e === "object" &&
61
+ e !== null &&
62
+ "issues" in e &&
63
+ Array.isArray((e as { issues?: unknown }).issues)
64
+ ) {
65
+ return jsonFromZod(e as never);
66
+ }
67
+ } catch {}
68
+
69
+ try {
70
+ return jsonFromPrisma(e);
71
+ } catch {
72
+ console.error(e);
73
+ return jsonOk(null, { message: "Unknown error" });
74
+ }
75
+ }
76
+ }
@@ -1,71 +1,78 @@
1
- import React from "react";
2
- import ServerActionForm from "./form-client";
3
- import { prisma } from "@/lib/prisma";
4
- import { z } from "zod";
5
- import { redirect } from "next/navigation";
6
-
7
- const schema = z.object({
8
- title: z.string().min(1, "Title is required"),
9
- content: z.string().optional(),
10
- });
11
-
12
- export default async function Page({ searchParams }: any) {
13
- async function createPost(formData: FormData) {
14
- "use server";
15
- const title = formData.get("title")?.toString() ?? "";
16
- const content = formData.get("content")?.toString() ?? "";
17
-
18
- const parsed = schema.safeParse({ title, content });
19
- if (!parsed.success) {
20
- // For simplicity in this example, throw an error which will surface in the server logs / dev overlay.
21
- // In a real app, you'd return structured errors or use progressive enhancement with client-side validation.
22
- throw new Error("Validation failed: " + parsed.error.message);
23
- }
24
-
25
- // NOTE: The Post model requires authorId. For this example we upsert a demo author so the FK is satisfied.
26
- // In real apps you should resolve the current user via NextAuth session.
27
- const fallbackAuthorId = "__demo_author__";
28
-
29
- await prisma.user.upsert({
30
- where: { id: fallbackAuthorId },
31
- update: {},
32
- create: {
33
- id: fallbackAuthorId,
34
- email: "demo@example.com",
35
- name: "Demo User",
36
- // No password for demo account
37
- },
38
- });
39
-
40
- await prisma.post.create({
41
- data: {
42
- title: parsed.data.title,
43
- content: parsed.data.content ?? null,
44
- authorId: fallbackAuthorId,
45
- },
46
- });
47
-
48
- redirect("/examples/forms/server-action?created=1");
49
- }
50
-
51
- const params = await searchParams;
52
- const created = (params && (params as any).created) || false;
53
-
54
- return (
55
- <div className="mx-auto max-w-xl p-6">
56
- <h1 className="mb-4 text-2xl font-bold">Server Action Example</h1>
57
- <p className="mb-4 text-sm text-muted-foreground">
58
- This demo shows a simple server action that validates with zod and
59
- creates a Post via prisma (uses a demo fallback authorId).
60
- </p>
61
- {/* Pass the server action down to the client form */}
62
- {/* @ts-ignore server action passed to client */}
63
- <ServerActionForm action={createPost} />
64
- {created && (
65
- <p className="mt-4 text-sm text-green-600">
66
- Post created successfully.
67
- </p>
68
- )}
69
- </div>
70
- );
71
- }
1
+ import React from "react";
2
+ import ServerActionForm from "./form-client";
3
+ import { prisma } from "@/lib/prisma";
4
+ import { z } from "zod";
5
+ import { redirect } from "next/navigation";
6
+
7
+ const schema = z.object({
8
+ title: z.string().min(1, "Title is required"),
9
+ content: z.string().optional(),
10
+ });
11
+
12
+ export default async function Page({
13
+ searchParams,
14
+ }: {
15
+ searchParams?: Promise<Record<string, string | string[] | undefined>>;
16
+ }) {
17
+ async function createPost(formData: FormData) {
18
+ "use server";
19
+ const title = formData.get("title")?.toString() ?? "";
20
+ const content = formData.get("content")?.toString() ?? "";
21
+
22
+ const parsed = schema.safeParse({ title, content });
23
+ if (!parsed.success) {
24
+ // For simplicity in this example, throw an error which will surface in the server logs / dev overlay.
25
+ // In a real app, you'd return structured errors or use progressive enhancement with client-side validation.
26
+ throw new Error("Validation failed: " + parsed.error.message);
27
+ }
28
+
29
+ // NOTE: The Post model requires authorId. For this example we upsert a demo author so the FK is satisfied.
30
+ // In real apps you should resolve the current user via NextAuth session.
31
+ const fallbackAuthorId = "__demo_author__";
32
+
33
+ await prisma.user.upsert({
34
+ where: { id: fallbackAuthorId },
35
+ update: {},
36
+ create: {
37
+ id: fallbackAuthorId,
38
+ email: "demo@example.com",
39
+ name: "Demo User",
40
+ // No password for demo account
41
+ },
42
+ });
43
+
44
+ await prisma.post.create({
45
+ data: {
46
+ title: parsed.data.title,
47
+ content: parsed.data.content ?? null,
48
+ authorId: fallbackAuthorId,
49
+ },
50
+ });
51
+
52
+ redirect("/examples/forms/server-action?created=1");
53
+ }
54
+
55
+ const params = (await searchParams) ?? {};
56
+ const createdRaw = params.created;
57
+ const created = Array.isArray(createdRaw)
58
+ ? createdRaw[0] === "1"
59
+ : createdRaw === "1";
60
+
61
+ return (
62
+ <div className="mx-auto max-w-xl p-6">
63
+ <h1 className="mb-4 text-2xl font-bold">Server Action Example</h1>
64
+ <p className="mb-4 text-sm text-muted-foreground">
65
+ This demo shows a simple server action that validates with zod and
66
+ creates a Post via prisma (uses a demo fallback authorId).
67
+ </p>
68
+ {/* Pass the server action down to the client form */}
69
+ <ServerActionForm action={createPost} />
70
+
71
+ {created && (
72
+ <p className="mt-4 text-sm text-green-600">
73
+ Post created successfully.
74
+ </p>
75
+ )}
76
+ </div>
77
+ );
78
+ }
@@ -1,79 +1,85 @@
1
- "use client";
2
-
3
- import { useEffect, useState, useRef } from "react";
4
-
5
- type UseCheckUniqueResult = {
6
- loading: boolean;
7
- unique: boolean | null; // null = unknown / not checked
8
- error: string | null;
9
- };
10
-
11
- export default function useCheckUnique(
12
- field: string,
13
- value: string | undefined | null,
14
- delay = 500,
15
- ): UseCheckUniqueResult {
16
- const [loading, setLoading] = useState(false);
17
- const [unique, setUnique] = useState<boolean | null>(null);
18
- const [error, setError] = useState<string | null>(null);
19
- const timer = useRef<number | undefined>(undefined);
20
- const controllerRef = useRef<AbortController | null>(null);
21
-
22
- useEffect(() => {
23
- // Clear previous timer / request
24
- if (timer.current) window.clearTimeout(timer.current);
25
- if (controllerRef.current) controllerRef.current.abort();
26
-
27
- // Reset state for empty values
28
- if (!value) {
29
- setLoading(false);
30
- setUnique(null);
31
- setError(null);
32
- return;
33
- }
34
-
35
- setLoading(true);
36
- setError(null);
37
-
38
- timer.current = window.setTimeout(() => {
39
- const controller = new AbortController();
40
- controllerRef.current = controller;
41
-
42
- (async () => {
43
- try {
44
- const res = await fetch("/api/users/check-unique", {
45
- method: "POST",
46
- headers: { "Content-Type": "application/json" },
47
- body: JSON.stringify({ field, value }),
48
- signal: controller.signal,
49
- });
50
- const payload = await res.json().catch(() => null);
51
- if (!res.ok || !payload) {
52
- setError("Failed to validate");
53
- setUnique(null);
54
- } else if (payload?.success) {
55
- // Expecting { success: true, data: { unique: boolean } }
56
- setUnique(Boolean(payload.data?.unique));
57
- } else {
58
- setError(payload?.message || "Validation failed");
59
- setUnique(null);
60
- }
61
- } catch (e: unknown) {
62
- if ((e as any)?.name === "AbortError") return;
63
- setError("Validation failed");
64
- setUnique(null);
65
- } finally {
66
- setLoading(false);
67
- controllerRef.current = null;
68
- }
69
- })();
70
- }, delay);
71
-
72
- return () => {
73
- if (timer.current) window.clearTimeout(timer.current);
74
- if (controllerRef.current) controllerRef.current.abort();
75
- };
76
- }, [field, value, delay]);
77
-
78
- return { loading, unique, error };
79
- }
1
+ "use client";
2
+
3
+ import { useEffect, useState, useRef } from "react";
4
+
5
+ type UseCheckUniqueResult = {
6
+ loading: boolean;
7
+ unique: boolean | null; // null = unknown / not checked
8
+ error: string | null;
9
+ };
10
+
11
+ export default function useCheckUnique(
12
+ field: string,
13
+ value: string | undefined | null,
14
+ delay = 500,
15
+ ): UseCheckUniqueResult {
16
+ const [loading, setLoading] = useState(false);
17
+ const [unique, setUnique] = useState<boolean | null>(null);
18
+ const [error, setError] = useState<string | null>(null);
19
+ const timer = useRef<number | undefined>(undefined);
20
+ const controllerRef = useRef<AbortController | null>(null);
21
+
22
+ useEffect(() => {
23
+ // Clear previous timer / request
24
+ if (timer.current) window.clearTimeout(timer.current);
25
+ if (controllerRef.current) controllerRef.current.abort();
26
+
27
+ // Reset state for empty values
28
+ if (!value) {
29
+ setLoading(false);
30
+ setUnique(null);
31
+ setError(null);
32
+ return;
33
+ }
34
+
35
+ setLoading(true);
36
+ setError(null);
37
+
38
+ timer.current = window.setTimeout(() => {
39
+ const controller = new AbortController();
40
+ controllerRef.current = controller;
41
+
42
+ (async () => {
43
+ try {
44
+ const res = await fetch("/api/users/check-unique", {
45
+ method: "POST",
46
+ headers: { "Content-Type": "application/json" },
47
+ body: JSON.stringify({ field, value }),
48
+ signal: controller.signal,
49
+ });
50
+ const payload = await res.json().catch(() => null);
51
+ if (!res.ok || !payload) {
52
+ setError("Failed to validate");
53
+ setUnique(null);
54
+ } else if (payload?.success) {
55
+ // Expecting { success: true, data: { unique: boolean } }
56
+ setUnique(Boolean(payload.data?.unique));
57
+ } else {
58
+ setError(payload?.message || "Validation failed");
59
+ setUnique(null);
60
+ }
61
+ } catch (e: unknown) {
62
+ if (
63
+ e &&
64
+ typeof e === "object" &&
65
+ "name" in e &&
66
+ (e as { name?: unknown }).name === "AbortError"
67
+ )
68
+ return;
69
+ setError("Validation failed");
70
+ setUnique(null);
71
+ } finally {
72
+ setLoading(false);
73
+ controllerRef.current = null;
74
+ }
75
+ })();
76
+ }, delay);
77
+
78
+ return () => {
79
+ if (timer.current) window.clearTimeout(timer.current);
80
+ if (controllerRef.current) controllerRef.current.abort();
81
+ };
82
+ }, [field, value, delay]);
83
+
84
+ return { loading, unique, error };
85
+ }
@@ -1,28 +1,28 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { useFormField } from "./context";
5
- import { Slot } from "@radix-ui/react-slot";
6
-
7
- export interface FormControlProps
8
- extends React.ComponentPropsWithoutRef<typeof Slot> {}
9
-
10
- export const FormControl = React.forwardRef<
11
- React.ElementRef<typeof Slot>,
12
- FormControlProps
13
- >(({ ...props }, ref) => {
14
- const { formItemId, formDescriptionId, formMessageId, error } =
15
- useFormField();
16
- return (
17
- <Slot
18
- ref={ref}
19
- id={formItemId}
20
- aria-describedby={
21
- error ? `${formDescriptionId} ${formMessageId}` : formDescriptionId
22
- }
23
- aria-invalid={!!error}
24
- {...props}
25
- />
26
- );
27
- });
28
- FormControl.displayName = "FormControl";
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useFormField } from "./context";
5
+ import { Slot } from "@radix-ui/react-slot";
6
+
7
+ export type FormControlProps = React.ComponentPropsWithoutRef<typeof Slot>;
8
+
9
+
10
+ export const FormControl = React.forwardRef<
11
+ React.ElementRef<typeof Slot>,
12
+ FormControlProps
13
+ >(({ ...props }, ref) => {
14
+ const { formItemId, formDescriptionId, formMessageId, error } =
15
+ useFormField();
16
+ return (
17
+ <Slot
18
+ ref={ref}
19
+ id={formItemId}
20
+ aria-describedby={
21
+ error ? `${formDescriptionId} ${formMessageId}` : formDescriptionId
22
+ }
23
+ aria-invalid={!!error}
24
+ {...props}
25
+ />
26
+ );
27
+ });
28
+ FormControl.displayName = "FormControl";
@@ -1,22 +1,23 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { useFormField } from "./context";
5
- import { cn } from "@/lib/utils";
6
-
7
- export interface FormDescriptionProps
8
- extends React.HTMLAttributes<HTMLParagraphElement> {}
9
-
10
- export const FormDescription = ({
11
- className,
12
- ...props
13
- }: FormDescriptionProps) => {
14
- const { formDescriptionId } = useFormField();
15
- return (
16
- <p
17
- id={formDescriptionId}
18
- className={cn("text-xs text-muted-foreground", className)}
19
- {...props}
20
- />
21
- );
22
- };
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useFormField } from "./context";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ export type FormDescriptionProps =
8
+ React.HTMLAttributes<HTMLParagraphElement>;
9
+
10
+
11
+ export const FormDescription = ({
12
+ className,
13
+ ...props
14
+ }: FormDescriptionProps) => {
15
+ const { formDescriptionId } = useFormField();
16
+ return (
17
+ <p
18
+ id={formDescriptionId}
19
+ className={cn("text-xs text-muted-foreground", className)}
20
+ {...props}
21
+ />
22
+ );
23
+ };