aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikhil M Warrier <nikhilmw.dev@gmail.com>2026-03-15 12:41:11 +0530
committerNikhil M Warrier <nikhilmw.dev@gmail.com>2026-03-15 12:41:11 +0530
commit3ff6f60638f0e535eecb259846ea9e405effcc82 (patch)
tree2f273ad002432996f8638fb1e12d504794d314d9
parentd17de1c5bc58531a4d971f873f2577e5d1f92ffe (diff)
Add `select`, `radio`, `checkbox` to forms
-rw-r--r--src/components/FormField.astro76
-rw-r--r--src/content.config.ts13
-rw-r--r--src/content/forms/test.mdx14
-rw-r--r--src/pages/forms/[...id].astro53
-rw-r--r--src/styles/global.css2
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'] {