بلاک‌ها · پشتیبانی و محتوا

تماس با استودیو

پیش‌نمایش تمام‌صفحه

تماسِ تیره: کلمهٔ درشتِ «تماس» پشتِ صفحه، سه قول و شبکه‌های اجتماعی، فرمِ شیشه‌ای با اعتبارسنجی و پیامِ موفقیت، و سه کارتِ راهِ ارتباطی.

۱. نصب کامپوننت‌ها

این بلاک از ۶ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeiconspersiantext-fieldtextarea

پکیج‌منیجر پیش‌فرض 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/textarea.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

contact-studio.tsx
"use client"

import * as React from "react"

import { ArrowUpIcon, CheckIcon, EnvelopeIcon, GlobeIcon, InstagramLogo, LinkedInLogo, PhoneIcon, TelegramLogo } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { TextField } from "@/components/ui/text-field"
import { Textarea } from "@/components/ui/textarea"
import { toFaDigits } from "@/lib/persian"

const PROMISES = ["پاسخ در یک روزِ کاری", "یک نفر از اول تا آخر کنارتان", "پشتیبانی تا مدت‌ها پس از تحویل"]
const CHANNELS = [
  { icon: <PhoneIcon />, title: "تماس با استودیو", value: toFaDigits("021-88776655"), href: "tel:02188776655", ltr: true },
  { icon: <GlobeIcon />, title: "کجا هستیم", value: "تهران، خیابانِ ایرانشهر، کوچهٔ نوزدهم", href: "#" },
  { icon: <EnvelopeIcon />, title: "ایمیل", value: "hello@noor.studio", href: "mailto:hello@noor.studio", ltr: true },
]

/**
 * تماس با استودیو، تیره: کلمهٔ درشتِ «تماس» کم‌رنگ پشتِ صفحه، سمتی «سلام کنید» با سه قول و
 * شبکه‌های اجتماعی، سمتِ دیگر فرمِ شیشه‌ای (نام، ایمیل، پیام) با اعتبارسنجی و پیامِ موفقیت؛
 * زیرش سه کارتِ راهِ ارتباطی. همیشه تیره است.
 */
export default function ContactStudioBlock() {
  const [errors, setErrors] = React.useState<Record<string, string>>({})
  const [sent, setSent] = React.useState(false)

  const submit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    const data = new FormData(event.currentTarget)
    const next: Record<string, string> = {}
    if (!String(data.get("name")).trim()) next.name = "نامتان را بنویسید."
    if (!/^\S+@\S+\.\S+$/.test(String(data.get("email")))) next.email = "ایمیل را کامل بنویسید."
    if (String(data.get("message")).trim().length < 10) next.message = "کمی بیشتر از پروژه بگویید."
    setErrors(next)
    if (Object.keys(next).length === 0) setSent(true)
  }

  return (
    <ControlSizeProvider size="xl" asChild>
      <section
        aria-labelledby="cs-title"
        className="relative isolate w-full overflow-hidden bg-[var(--dig-graphite-950)] px-4 py-20 text-[var(--dig-white)] [--background:var(--dig-graphite-900)] [--border:var(--dig-graphite-700)] [--card:var(--dig-graphite-900)] [--foreground:var(--dig-white)] [--input:var(--dig-graphite-700)] [--muted-foreground:var(--dig-neutral-400)] [--primary-foreground:var(--dig-graphite-950)] [--primary:var(--dig-white)] sm:px-6 sm:py-28"
      >
        <span aria-hidden className="pointer-events-none absolute inset-x-0 top-10 -z-10 text-center text-[clamp(5rem,18vw,15rem)] leading-none font-black text-[var(--dig-graphite-800)] select-none">
          تماس
        </span>
        <div className="mx-auto grid w-full max-w-6xl grid-cols-1 gap-12 lg:grid-cols-[1fr_1.3fr]">
          <div className="flex flex-col items-start gap-6 pt-4">
            <h2 id="cs-title" className="flex items-center gap-3 text-4xl font-black sm:text-5xl">
              سلام کنید
              <ArrowUpIcon aria-hidden className="size-9 -rotate-45" />
            </h2>
            <p className="max-w-sm leading-8 text-muted-foreground">پروژه‌ای دارید یا فقط می‌خواهید سلام کنید؟ چند خط بنویسید؛ همهٔ پیام‌ها را خودمان می‌خوانیم.</p>
            <ul className="flex flex-col gap-3">
              {PROMISES.map((item) => (
                <li key={item} className="flex items-center gap-3">
                  <span aria-hidden className="flex size-7 items-center justify-center rounded-full border border-[var(--dig-graphite-700)] bg-[var(--dig-graphite-900)]">
                    <CheckIcon className="size-4" />
                  </span>
                  {item}
                </li>
              ))}
            </ul>
            <div className="flex gap-2">
              {[
                { icon: <InstagramLogo />, label: "اینستاگرام" },
                { icon: <LinkedInLogo />, label: "لینکدین" },
                { icon: <TelegramLogo />, label: "تلگرام" },
              ].map((social) => (
                <Button key={social.label} variant="outline" size="icon" aria-label={social.label} asChild>
                  <a href="#">{social.icon}</a>
                </Button>
              ))}
            </div>
          </div>

          <div className="rounded-xl border border-[var(--dig-graphite-800)] bg-[color-mix(in_oklab,var(--dig-graphite-900)_80%,transparent)] p-5 shadow-2xl backdrop-blur-md sm:p-6">
            {sent ? (
              <div role="status" className="flex min-h-80 flex-col items-center justify-center gap-3 text-center">
                <span aria-hidden className="flex size-14 items-center justify-center rounded-full bg-[var(--dig-white)] text-[var(--dig-graphite-950)]">
                  <CheckIcon className="size-7" />
                </span>
                <p className="text-xl font-bold">پیامتان رسید</p>
                <p className="text-muted-foreground">تا پایانِ روزِ کاریِ بعد جواب می‌دهیم.</p>
              </div>
            ) : (
              <form noValidate onSubmit={submit} className="flex flex-col gap-4">
                <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
                  <TextField name="name" label="نام" variant="flat" invalid={!!errors.name} errorMessage={errors.name} />
                  <TextField name="email" type="email" dir="ltr" label="ایمیل" variant="flat" invalid={!!errors.email} errorMessage={errors.email} />
                </div>
                <div className="flex flex-col gap-2">
                  <label htmlFor="cs-message" className="text-sm font-medium">
                    پیام
                  </label>
                  <Textarea id="cs-message" name="message" variant="flat" rows={6} aria-invalid={!!errors.message} aria-describedby={errors.message ? "cs-message-error" : undefined} />
                  {errors.message && (
                    <p id="cs-message-error" className="text-sm text-[var(--dig-red-300)]">
                      {errors.message}
                    </p>
                  )}
                </div>
                <Button type="submit" radius="full" fullWidth>
                  ارسال
                </Button>
              </form>
            )}
          </div>
        </div>

        <ul className="mx-auto mt-14 grid w-full max-w-6xl grid-cols-1 gap-3 md:grid-cols-3">
          {CHANNELS.map((channel) => (
            <li key={channel.title}>
              <a href={channel.href} className="group flex h-full flex-col gap-6 rounded-xl border border-[var(--dig-graphite-800)] bg-[var(--dig-graphite-900)] p-5 outline-none transition-colors hover:border-[var(--dig-graphite-600)] focus-visible:ring-2 focus-visible:ring-[var(--dig-white)]">
                <span aria-hidden className="flex size-10 items-center justify-center rounded-lg bg-[var(--dig-graphite-800)] [&>svg]:size-5">
                  {channel.icon}
                </span>
                <span className="flex flex-col gap-1">
                  <span className="font-bold">{channel.title}</span>
                  {channel.ltr ? (
                    <bdi dir="ltr" className="text-start text-sm text-muted-foreground">
                      {channel.value}
                    </bdi>
                  ) : (
                    <span className="text-sm text-muted-foreground">{channel.value}</span>
                  )}
                </span>
              </a>
            </li>
          ))}
        </ul>
      </section>
    </ControlSizeProvider>
  )
}