src/components/uiable/blocks/content/content-16/content-16.tsx
"use client"
import {
useState,
useEffect,
useRef,
useCallback,
ChangeEvent,
FormEvent,
ComponentType,
CSSProperties,
RefObject,
} from "react"
// next
import Link from "next/link"
// shadcn
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import { Textarea } from "@/components/ui/textarea"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
// third-party
import { cn } from "cn"
import { motion, AnimatePresence } from "framer-motion"
// assets
import {
IconBrandGithub,
IconBrandLinkedin,
IconBrandTwitter,
IconBrandDribbble,
} from "@tabler/icons-react"
import {
Mail,
MapPin,
Clock,
Phone,
ArrowUpRight,
Check,
ArrowRight,
LucideIcon,
} from "lucide-react"
interface FormState {
name: string
email: string
subject: string
message: string
}
interface SocialLink {
name: string
href: string
icon: ComponentType<{ className?: string; style?: CSSProperties }>
brandColor?: string
isGithub?: boolean
}
const TYPEWRITER_PHRASES = [
"something together.",
"future products.",
"scalable systems.",
"next-gen apps.",
] as const
const SUBJECT_OPTIONS = [
"Full App Development",
"Component System Integration",
"UI/UX Design Scoping",
"General Inquiry",
] as const
const SOCIAL_LINKS: SocialLink[] = [
{
name: "LINKEDIN",
href: "#",
icon: IconBrandLinkedin,
brandColor: "#0A66C2",
},
{
name: "TWITTER / X",
href: "#",
icon: IconBrandTwitter,
brandColor: "#1DA1F2",
},
{
name: "GITHUB",
href: "#",
icon: IconBrandGithub,
isGithub: true,
},
{
name: "DRIBBBLE",
href: "#",
icon: IconBrandDribbble,
brandColor: "#EA4C89",
},
]
const INITIAL_FORM: FormState = {
name: "",
email: "",
subject: "General Inquiry",
message: "",
}
function TypewriterHeadline() {
const [phraseIndex, setPhraseIndex] = useState(0)
const [currentText, setCurrentText] = useState("")
const [isDeleting, setIsDeleting] = useState(false)
useEffect(() => {
const phrase = TYPEWRITER_PHRASES[phraseIndex]
if (!isDeleting && currentText === phrase) {
const t = setTimeout(() => setIsDeleting(true), 2400)
return () => clearTimeout(t)
}
if (isDeleting && currentText === "") {
const t = setTimeout(() => {
setIsDeleting(false)
setPhraseIndex((p) => (p + 1) % TYPEWRITER_PHRASES.length)
}, 0)
return () => clearTimeout(t)
}
const t = setTimeout(
() => {
setCurrentText(
isDeleting
? phrase.substring(0, currentText.length - 1)
: phrase.substring(0, currentText.length + 1)
)
},
isDeleting ? 35 : 75
)
return () => clearTimeout(t)
}, [currentText, isDeleting, phraseIndex])
return (
<h1 className="flex flex-col items-center justify-center text-center text-6xl sm:text-8xl lg:items-start lg:text-left lg:text-6xl xl:text-7xl">
<span>Let's build</span>
<div className="flex min-h-[1.5em] items-center justify-center pt-1 text-center text-4xl sm:text-5xl lg:justify-start lg:text-left lg:text-4xl xl:text-5xl">
<span className="bg-gradient-to-r from-primary via-primary/80 to-primary/50 bg-clip-text pb-2 font-extrabold whitespace-nowrap text-transparent">
{currentText}
</span>
<span className="ml-1.5 inline-block h-[0.8em] w-[3px] shrink-0 animate-pulse rounded-full bg-primary" />
</div>
</h1>
)
}
interface ContactInfo {
id: string
icon: LucideIcon
title: string
value: string
href?: string
valueClassName?: string
containerGap?: string
}
const CONTACT_INFO_DATA: ContactInfo[] = [
{
id: "email",
icon: Mail,
title: "Inquiries & Proposals",
value: "build@aistudio-components.dev",
href: "mailto:build@aistudio-components.dev",
valueClassName:
"relative w-fit transition-colors after:absolute after:bottom-0 after:left-1/2 after:h-px after:w-0 after:-translate-x-1/2 after:bg-primary after:transition-all after:duration-300 group-hover:text-primary group-hover:after:w-full",
},
{
id: "location",
icon: MapPin,
title: "Global HQ",
value: "San Francisco, CA • Distributed Engineering",
},
{
id: "phone",
icon: Phone,
title: "Direct Desk",
value: "+1 (415) 555-0199",
href: "tel:+14155550199",
valueClassName: "transition-colors group-hover:text-primary",
},
{
id: "time",
icon: Clock,
title: "Pacific Desk Time",
value: "06:00 PM IST",
containerGap: "gap-1",
valueClassName: "text-sm",
},
]
function ContactInfoItem({ item }: { item: ContactInfo }) {
const Icon = item.icon
const wrapperClasses = cn(
"-ml-2 flex items-center gap-4 rounded-lg p-2",
item.href &&
"group transition-colors hover:bg-muted/50 focus-visible:bg-muted/50 focus-visible:outline-none"
)
const Wrapper: any = item.href ? Link : "div"
const props = item.href ? { href: item.href } : {}
return (
<Wrapper className={wrapperClasses} {...props}>
<div
aria-hidden="true"
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-primary/30 bg-primary/10 text-primary",
item.href && "transition-colors group-hover:bg-primary/20"
)}
>
<Icon className="h-4 w-4 stroke-[1.5]" />
</div>
<div className={cn("flex flex-col gap-1 p-1", item.containerGap)}>
<span className="text-xs font-bold tracking-wider text-muted-foreground uppercase">
{item.title}
</span>
<span
className={cn(
"text-base font-semibold text-foreground",
item.valueClassName
)}
>
{item.value}
</span>
</div>
</Wrapper>
)
}
interface FormFieldProps {
id: string
field: string
label: string
type?: string
isTextarea?: boolean
value: string
focusedField: string | null
onFocus: (field: string) => void
onBlur: () => void
onChange: (e: any) => void
textareaRef?: RefObject<HTMLTextAreaElement | null>
}
function FormField({
id,
field,
label,
type = "text",
isTextarea = false,
value,
focusedField,
onFocus,
onBlur,
onChange,
textareaRef,
}: FormFieldProps) {
const isFocused = focusedField === field
const isActive = isFocused || value.length > 0
const baseClasses =
"w-full rounded-t-sm border-x-0 border-t-0 border-b-2 border-border bg-muted/50 px-4 py-3.5 text-base font-medium text-foreground shadow-none transition-all focus:bg-background focus:outline-none focus-visible:ring-0"
return (
<div className="group relative flex flex-col">
<Label
htmlFor={id}
className={cn(
"pointer-events-none font-semibold transition-all duration-200",
isActive
? "mb-1 text-xs text-primary"
: "mb-1.5 text-sm text-muted-foreground"
)}
>
{label}
</Label>
<div className="relative w-full">
{isTextarea ? (
<Textarea
id={id}
ref={textareaRef}
value={value}
onFocus={() => onFocus(field)}
onBlur={onBlur}
onChange={onChange}
rows={3}
required
className={cn(
baseClasses,
"min-h-[50px] resize-none overflow-hidden"
)}
/>
) : (
<Input
id={id}
type={type}
value={value}
onFocus={() => onFocus(field)}
onBlur={onBlur}
onChange={onChange}
required
className={cn(baseClasses, "h-auto")}
/>
)}
<span
className={cn(
"absolute bottom-0 left-0 h-[2px] bg-primary transition-all duration-300",
isFocused ? "w-full" : "w-0"
)}
/>
</div>
</div>
)
}
interface SuccessMessageProps {
name: string
email: string
onReset: () => void
}
function SuccessMessage({ name, email, onReset }: SuccessMessageProps) {
return (
<motion.div
key="success-state"
initial="hidden"
animate="visible"
exit={{ opacity: 0, scale: 0.98, transition: { duration: 0.4 } }}
variants={{
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1, delayChildren: 0.1 },
},
}}
className="relative my-auto flex w-full flex-col gap-8 overflow-hidden rounded-3xl border border-primary/10 bg-gradient-to-br from-background/90 to-muted/90 p-10 shadow-2xl shadow-primary/5 backdrop-blur-lg sm:p-14"
>
{/* Ambient background glows */}
<div className="absolute -top-32 -left-32 h-64 w-64 rounded-full bg-primary/10 blur-3xl" />
<div className="absolute -right-32 -bottom-32 h-64 w-64 rounded-full bg-primary/10 blur-3xl" />
<motion.div
variants={{
hidden: { opacity: 0, scale: 0.5, rotate: -20 },
visible: {
opacity: 1,
scale: 1,
rotate: 0,
transition: { type: "spring", bounce: 0.5 },
},
}}
className="relative z-10 flex h-16 w-16 items-center justify-center rounded-2xl border border-primary/30 bg-primary/10 text-primary shadow-[0_0_30px_-5px_hsl(var(--primary)/0.3)]"
>
<Check className="h-8 w-8 stroke-[2.5]" />
<div
className="absolute inset-0 animate-ping rounded-2xl border border-primary/20"
style={{ animationDuration: "3s" }}
/>
</motion.div>
<div className="relative z-10 flex flex-col gap-4">
<motion.span
variants={{
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 },
}}
className="flex items-center gap-2 text-xs font-bold tracking-widest text-primary uppercase"
>
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-primary" />
Message Transmitted
</motion.span>
<motion.h2
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}}
className="text-3xl font-extrabold tracking-tight text-foreground sm:text-5xl"
>
Thank you,{" "}
<span className="bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent">
{name.split(" ")[0]}
</span>
.
</motion.h2>
<motion.p
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}}
className="max-w-xl text-base leading-relaxed text-muted-foreground sm:text-lg"
>
Your note has been securely logged. An engineer from our core team
will review your inquiry and respond to{" "}
<strong className="font-semibold text-foreground underline decoration-primary/30 underline-offset-4">
{email}
</strong>{" "}
within 4 business hours.
</motion.p>
</div>
<motion.div
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}}
className="relative z-10 pt-4"
>
<Button
type="button"
onClick={onReset}
className="group relative flex h-12 w-fit items-center justify-center gap-3 overflow-hidden rounded-full bg-primary/10 px-8 text-sm font-bold tracking-wider text-primary uppercase transition-all duration-300 hover:bg-primary hover:text-primary-foreground hover:shadow-[0_0_20px_-3px_hsl(var(--primary)/0.4)] active:scale-95"
>
<span>Send another message</span>
<ArrowRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" />
</Button>
</motion.div>
</motion.div>
)
}
// ------------------------------ | CONTACT 16 | ------------------------------ //
export default function Contact16() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [submitted, setSubmitted] = useState(false)
const [form, setForm] = useState<FormState>(INITIAL_FORM)
const [focusedField, setFocusedField] = useState<string | null>(null)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const handleFocus = useCallback((key: string) => setFocusedField(key), [])
const handleBlur = useCallback(() => {
setFocusedField(null)
}, [])
const handleFieldChange = useCallback(
(key: keyof FormState) => (value: string) => {
setForm((prev) => ({ ...prev, [key]: value }))
},
[]
)
const handleMessageChange = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
setForm((prev) => ({ ...prev, message: e.target.value }))
const el = textareaRef.current
if (el) {
requestAnimationFrame(() => {
el.style.height = "auto"
el.style.height = `${el.scrollHeight}px`
})
}
},
[]
)
const handleSubmit = useCallback((e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
setIsSubmitting(true)
setTimeout(() => {
setIsSubmitting(false)
setSubmitted(true)
}, 1400)
}, [])
const handleReset = useCallback(() => {
setSubmitted(false)
setForm(INITIAL_FORM)
}, [])
const handleSubjectChange = useCallback((val: string[]) => {
if (val.length > 0) setForm((prev) => ({ ...prev, subject: val[0] }))
}, [])
return (
<div className="w-full">
<section className="relative flex min-h-screen w-full flex-col justify-between overflow-hidden bg-background p-6 text-foreground transition-colors duration-500 sm:p-12 lg:p-20">
<div className="relative mx-auto my-12 grid w-full max-w-7xl grid-cols-1 items-start gap-12 lg:my-20 lg:grid-cols-12 lg:gap-20">
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
className="flex flex-col gap-5 lg:col-span-5"
>
<div className="flex flex-col items-center justify-center gap-4 lg:items-start">
<TypewriterHeadline />
<p className="max-w-md text-center text-base text-muted-foreground sm:text-lg md:max-w-xl md:text-xl lg:max-w-2xl lg:text-left lg:text-base">
Have a new project in mind, a complex technical challenge, or
simply want to explore engineering collaboration? Drop us a
line.
</p>
</div>
<Separator />
<div className="flex flex-col gap-8 pt-6">
{CONTACT_INFO_DATA.map((item) => (
<ContactInfoItem key={item.id} item={item} />
))}
</div>
<Separator />
<div className="flex flex-col gap-4 pt-6">
<span className="text-xs font-bold tracking-widest text-muted-foreground uppercase">
Connect Directly
</span>
<div className="flex flex-wrap gap-x-6 gap-y-3">
{SOCIAL_LINKS.map((s) => {
const Icon = s.icon
return (
<Link
key={s.name}
href={s.href}
target="_blank"
rel="noreferrer"
className="group relative flex items-center gap-2 py-1 text-xs font-bold tracking-widest text-foreground uppercase transition-colors hover:text-primary"
>
<div
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-md transition-transform group-hover:scale-110 ${s.isGithub ? "bg-foreground/10 text-foreground" : ""}`}
style={
!s.isGithub
? { backgroundColor: `${s.brandColor}22` }
: undefined
}
>
<Icon
className="h-3.5 w-3.5"
style={
!s.isGithub ? { color: s.brandColor } : undefined
}
/>
</div>
<span>{s.name}</span>
<span
className={`absolute bottom-0 left-1/2 h-[1.5px] w-0 -translate-x-1/2 transition-all duration-300 group-hover:w-full ${s.isGithub ? "bg-foreground" : ""}`}
style={
!s.isGithub
? { backgroundColor: s.brandColor }
: undefined
}
/>
</Link>
)
})}
</div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.1, ease: [0.16, 1, 0.3, 1] }}
className="flex w-full flex-col gap-8 lg:col-span-7"
>
<AnimatePresence mode="wait">
{submitted ? (
<SuccessMessage
name={form.name}
email={form.email}
onReset={handleReset}
/>
) : (
<motion.form
key="form-state"
onSubmit={handleSubmit}
noValidate
className="flex w-full flex-col gap-16 rounded-3xl border border-slate-300/50 bg-gradient-to-br from-background/90 to-muted/90 p-10 shadow-2xl shadow-primary/5 backdrop-blur-lg dark:border-slate-700/50"
>
<FormField
id="contact-name"
field="name"
label="Your Full Name *"
value={form.name}
focusedField={focusedField}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange("name")(e.target.value)
}
/>
<FormField
id="contact-email"
field="email"
label="Work Email Address *"
type="email"
value={form.email}
focusedField={focusedField}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange("email")(e.target.value)
}
/>
<div className="flex flex-col gap-3">
<span className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">
Project Nature / Inquiry Scope
</span>
<ToggleGroup
value={form.subject ? [form.subject] : []}
onValueChange={handleSubjectChange}
spacing={3}
className="flex flex-wrap"
>
{SUBJECT_OPTIONS.map((item) => (
<ToggleGroupItem
key={item}
value={item}
variant="outline"
className="h-auto rounded-full border-border/40 bg-muted/30 px-5 py-2.5 text-xs font-semibold tracking-wider text-muted-foreground uppercase shadow-none transition-all duration-300 hover:-translate-y-0.5 hover:border-primary/50 hover:bg-muted hover:text-foreground active:scale-95 data-[state=on]:border-primary data-[state=on]:bg-primary/10 data-[state=on]:text-primary data-[state=on]:shadow-[0_0_15px_-3px_hsl(var(--primary)/0.2)] data-[state=on]:hover:border-primary/80 data-[state=on]:hover:bg-primary/20"
>
{item}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<FormField
id="contact-message"
field="message"
label="Project Details & Requirements *"
isTextarea
value={form.message}
focusedField={focusedField}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={handleMessageChange}
textareaRef={textareaRef}
/>
<div className="pt-4">
<Button
type="submit"
disabled={isSubmitting}
className="group relative flex h-14 w-full items-center justify-center gap-2.5 rounded-none text-sm font-bold tracking-wider uppercase transition-all duration-300 hover:scale-[1.01] active:scale-[0.99]"
>
<span>
{isSubmitting
? "Transmitting..."
: "Submit Transmission"}
</span>
<ArrowUpRight className="h-4 w-4 shrink-0 transform transition-transform duration-300 group-hover:translate-x-1 group-hover:-translate-y-1" />
</Button>
</div>
</motion.form>
)}
</AnimatePresence>
</motion.div>
</div>
</section>
</div>
)
}