انتقل إلى المحتوى
TopClass UI

مكوّنك الأول

نموذج يُعرض على الخادم ويُرسل إلى إجراء خادم. لا JavaScript لبنيته.

ما الذي ستبنيه

نموذج اشتراك: حقل نص Input داخل Field، وزر Button.

1. الإجراء

يتحقّق Zod من البيانات على الخادم، فلا يمكن تجاوزه ولا يكلّف المتصفح شيئًا. تخرج fieldErrors مفهرسة باسم الحقل، وهذا ما يتوقّعه Field. الوسيط الأول هو الجواب السابق، وتقرؤه الخطوة 4.

app/subscribe/actions.ts
'use server'
import { z } from 'zod'
import type { FormActionState } from 'topclassui/form'

const schema = z.object({ email: z.email('Enter a valid email') })

export async function subscribe(_state: FormActionState, formData: FormData): Promise<FormActionState> {
  const parsed = schema.safeParse(Object.fromEntries(formData))
  if (!parsed.success) return { errors: z.flattenError(parsed.error).fieldErrors }

  await saveSubscriber(parsed.data.email)
  return {}
}

2. الصفحة

يربط Field التسمية والنص المساعد ورسالة الخطأ بعنصر الإدخال. وInput حقل إدخال أصلي، فيعمل قبل تحميل أي JavaScript.

app/subscribe/page.tsx
import { Field } from 'topclassui/field'
import { Input } from 'topclassui/input'
import { Button } from 'topclassui/button'
import { Form } from 'topclassui/form'
import { subscribe } from './actions'

export default function Page() {
  async function send(formData: FormData) {
    'use server'
    await subscribe({}, formData)
  }

  return (
    <Form action={send} className="flex flex-col gap-4">
      <Field name="email" label="Email" hint="We never share it" required>
        {(control) => <Input {...control} type="email" autoComplete="email" />}
      </Field>
      <Button type="submit">Subscribe</Button>
    </Form>
  )
}

3. ما الذي يجب أن تراه

  1. حقل نص له تسمية، وزر، بتنسيق الوضع الحالي. إذا ظهرا بلا تنسيق، فمدخل CSS ينقصه سطر أو ترتيبه خاطئ.
  2. عطّل JavaScript وأرسل النموذج. يُرسل النموذج مع ذلك، ويعمل الإجراء، وتُعاد تحميل الصفحة. هذا إجراء الخادم يؤدي عمله؛ أما الأخطاء التي يعيدها فتظهر في الخطوة 4.
  3. افتح لوحة الشبكة. لا سكربت للنموذج. Input وButton وField كلها تُشحن هنا بحجم 0 KB.

4. اعرض الخطأ بلا إعادة تحميل

انقل النموذج إلى ملف عميل واستخدم FormAction. تصل الأخطاء إلى الحقل المطابق.

app/subscribe/subscribe-form.tsx
'use client'
import { FormAction } from 'topclassui/form'
import { FieldLive } from 'topclassui/field'
import { Input } from 'topclassui/input'
import { Button } from 'topclassui/button'
import type { subscribe } from './actions'

export function SubscribeForm({ action }: { action: typeof subscribe }) {
  return (
    <FormAction action={action}>
      <FieldLive name="email" label="Email" required>
        {(control) => <Input {...control} type="email" />}
      </FieldLive>
      <Button type="submit">Subscribe</Button>
    </FormAction>
  )
}
app/subscribe/page.tsx، النسخة 2
import { subscribe } from './actions'
import { SubscribeForm } from './subscribe-form'

export default function Page() {
  return <SubscribeForm action={subscribe} />
}

مع Vite

النموذج نفسه مع دالة غير متزامنة عادية. هذا النموذج لا يعمل إلا بعد أن تحمّل الصفحة ملفات JavaScript الخاصة بها.

async function subscribe(formData: FormData) {
  await fetch('/api/subscribe', { method: 'POST', body: formData })
}

<form action={subscribe}>…</form>
التاليالألوان