فراخوانِ عضویت
پیشنمایش تمامصفحهبخشِ فراخوان با فرمِ ایمیل: کارتِ دوتکه با عکسِ واقعی، خطا زیرِ فیلد و پیامِ موفقیت در همان جا.
۱. نصب کامپوننتها
این بلاک از ۵ آیتم دیگ استفاده میکند: buttoncontrol-sizepersiantext-fieldtoast
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
cta-newsletter.tsx
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { TextField } from "@/components/ui/text-field"
import { toast } from "@/components/ui/toast"
import { isValidEmail } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"
/**
* فراخوانِ عضویت: کارتِ دوتکه با عکسِ واقعی و فرمِ ایمیل؛ خطا زیرِ همان فیلد و پیامِ
* موفقیت در همان جای فرم، تا کاربر از صفحه بیرون نرود.
*/
export default function CtaNewsletterBlock() {
const [done, setDone] = React.useState(false)
return (
<ControlSizeProvider size="xl" asChild>
<section className="mx-auto w-full max-w-6xl px-4 py-24 sm:px-6">
<div className="grid grid-cols-1 overflow-hidden rounded-xl border border-border bg-card md:grid-cols-2">
<div className="flex flex-col justify-center gap-6 p-8 sm:p-12">
<div className="flex flex-col gap-3">
<h2 className="text-3xl leading-snug font-black text-balance">هر پنجشنبه، یک نکتهٔ مالی</h2>
<p className="leading-8 text-muted-foreground">نامهای کوتاه با بهترین مقالههای هفته؛ بدونِ تبلیغ، هر وقت خواستی لغو کن.</p>
</div>
{done ? (
<p role="status" className="rounded-lg bg-success-subtle p-4 font-medium text-success-subtle-foreground">
عضو شدی؛ اولین نامه پنجشنبه میرسد.
</p>
) : (
<form
className="flex flex-col gap-3 sm:flex-row sm:items-start"
onSubmit={(event) => {
event.preventDefault()
setDone(true)
toast.success("عضویت ثبت شد.")
}}
>
<TextField
aria-label="ایمیل"
name="email"
type="email"
dir="ltr"
placeholder="name@example.com"
autoComplete="email"
required
validate={(value) => (isValidEmail(value) ? undefined : "ایمیل معتبر نیست؛ مثل name@example.com")}
className="flex-1"
/>
<Button type="submit">عضویت</Button>
</form>
)}
</div>
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages.coverNotebook.src} alt={stockImages.coverNotebook.alt} loading="lazy" className="order-first aspect-[16/9] size-full object-cover md:order-none md:aspect-auto" />
</div>
</section>
</ControlSizeProvider>
)
}