diff options
| author | Nikhil M Warrier <nikhilmw.dev@gmail.com> | 2026-03-15 12:41:11 +0530 |
|---|---|---|
| committer | Nikhil M Warrier <nikhilmw.dev@gmail.com> | 2026-03-15 12:41:11 +0530 |
| commit | 3ff6f60638f0e535eecb259846ea9e405effcc82 (patch) | |
| tree | 2f273ad002432996f8638fb1e12d504794d314d9 | |
| parent | d17de1c5bc58531a4d971f873f2577e5d1f92ffe (diff) | |
Add `select`, `radio`, `checkbox` to forms
| -rw-r--r-- | src/components/FormField.astro | 76 | ||||
| -rw-r--r-- | src/content.config.ts | 13 | ||||
| -rw-r--r-- | src/content/forms/test.mdx | 14 | ||||
| -rw-r--r-- | src/pages/forms/[...id].astro | 53 | ||||
| -rw-r--r-- | src/styles/global.css | 2 |
5 files changed, 122 insertions, 36 deletions
diff --git a/src/components/FormField.astro b/src/components/FormField.astro new file mode 100644 index 0000000..f03403d --- /dev/null +++ b/src/components/FormField.astro @@ -0,0 +1,76 @@ +--- +import type { CollectionEntry } from 'astro:content' + +interface Props { + field: CollectionEntry<'forms'>['data']['form']['fields'][number] +} + +const field = Astro.props.field +const fieldType = field.type as astroHTML.JSX.HTMLInputTypeAttribute +--- + +<div class="flex w-full flex-col gap-2"> + <label for={field.id} class="text-muted-foreground pl-1"> + {field.label} + </label> + + { + () => { + switch (field.type) { + case 'textarea': + return ( + <textarea + id={field.id} + name={field.id} + required={field.required} + rows="4" + class="border-border rounded-xl border px-4 py-2" + /> + ) + + case 'select': + return ( + <select + id={field.id} + name={field.id} + required={field.required} + class="border-border rounded-xl border px-4 py-2" + > + {field.options?.map((opt) => ( + <option value={opt}>{opt}</option> + ))} + </select> + ) + + case 'radio': + case 'checkbox': + return ( + <div class="flex flex-col gap-4 pl-1"> + {field.options?.map((opt, index) => ( + <label class="cursor-pointer"> + <input + type={fieldType} + name={field.id} + value={opt} + required={field.required && index === 0} + class="mr-2" + /> + {opt} + </label> + ))} + </div> + ) + default: + return ( + <input + type={fieldType || 'text'} + id={field.id} + name={field.id} + required={field.required} + class="border-border rounded-xl border px-4 py-2" + /> + ) + } + } + } +</div> diff --git a/src/content.config.ts b/src/content.config.ts index c9c4810..f0c8009 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -72,8 +72,19 @@ const forms = defineCollection({ }), label: z.string(), type: z - .enum(['text', 'email', 'number', 'textarea', 'tel', 'url']) + .enum([ + 'text', + 'email', + 'number', + 'textarea', + 'tel', + 'url', + 'select', + 'checkbox', + 'radio', + ]) .default('text'), + options: z.array(z.string()).optional(), required: z.boolean().default(false), }), ) diff --git a/src/content/forms/test.mdx b/src/content/forms/test.mdx index 6e3d731..d7990d8 100644 --- a/src/content/forms/test.mdx +++ b/src/content/forms/test.mdx @@ -18,9 +18,17 @@ form: label: "What's on your mind?" type: "textarea" required: true + - id: "graduation" + label: "Select graduation year" + type: "select" + options: + - '2026' + - '20"27' + - '2028' + - '2029' + + --- -# Let's talk open source. - -This is a sample form. +This is a sample form. Everything below this text is generated automatically from the metadata. diff --git a/src/pages/forms/[...id].astro b/src/pages/forms/[...id].astro index 713e599..d26c51d 100644 --- a/src/pages/forms/[...id].astro +++ b/src/pages/forms/[...id].astro @@ -1,7 +1,10 @@ --- +import FormField from '@/components/FormField.astro' import { FORM_POST_URL } from '@/consts' import Layout from '@/layouts/Layout.astro' import { getAllForms } from '@/lib/data-utils' +import { formatDate } from '@/lib/utils' +import { render } from 'astro:content' export async function getStaticPaths() { const forms = await getAllForms() @@ -11,50 +14,36 @@ export async function getStaticPaths() { })) } +const { Content } = await render(Astro.props) const form = Astro.props.data const fields = Astro.props.data.form.fields --- <Layout> <section - class="border-border mx-auto flex w-full max-w-3xl flex-col items-center gap-y-6 rounded-xl border p-8" + class="border-border mx-auto flex w-full max-w-3xl flex-col items-center gap-y-2 rounded-xl border p-8" > + <h1 + class="mb-2 w-full scroll-mt-31 text-center text-3xl leading-tight font-medium sm:text-4xl" + > + {form.title} + </h1> + <div + class="text-muted-foreground mb-4 mb-6 flex w-fit py-2 text-xs sm:px-2 sm:py-0 sm:text-sm" + > + <span>{formatDate(form.date)}</span> + </div> + + <article class="prose w-full"> + <Content /> + </article> + <form action={`${FORM_POST_URL}${form.id}`} method="POST" class="flex w-full flex-col items-start gap-12" > - <h1 class="w-full text-center text-3xl"> - {form.title} - </h1> - - { - fields.map((field) => ( - <div class="flex w-full flex-col gap-2"> - <label for={field.id} class="text-muted-foreground pl-1"> - {field.label} - </label> - - {field.type === 'textarea' ? ( - <textarea - id={field.id} - name={field.id} - required={field.required} - rows="4" - class="border-border rounded-xl border px-4 py-2" - /> - ) : ( - <input - type={field.type || 'text'} - id={field.id} - name={field.id} - required={field.required} - class="border-border rounded-xl border px-4 py-2" - /> - )} - </div> - )) - } + {fields.map((field) => <FormField field={field} />)} <div class="flex w-full justify-end"> <button diff --git a/src/styles/global.css b/src/styles/global.css index 4083ed3..2d4de9f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -79,6 +79,8 @@ --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --ring: oklch(0.708 0 0); + + accent-color: var(--primary); } [data-theme='dark'] { |
