Initial commit
This commit is contained in:
101
src/components/form/ContactForm.tsx
Normal file
101
src/components/form/ContactForm.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import TextAnimation from "@/components/text/TextAnimation";
|
||||
import EmailSignupForm from "@/components/form/EmailSignupForm";
|
||||
import Tag from "@/components/shared/Tag";
|
||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
import type { AnimationType } from "@/components/text/types";
|
||||
import { LucideIcon } from "lucide-react";
|
||||
|
||||
interface ContactFormProps {
|
||||
title: string;
|
||||
description: string;
|
||||
tag: string;
|
||||
tagIcon?: LucideIcon;
|
||||
useInvertedBackground: "noInvert" | "invertDefault" | "invertCard";
|
||||
inputPlaceholder?: string;
|
||||
buttonText?: string;
|
||||
termsText?: string;
|
||||
onSubmit?: (email: string) => void;
|
||||
centered?: boolean;
|
||||
className?: string;
|
||||
tagClassName?: string;
|
||||
titleClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
formWrapperClassName?: string;
|
||||
formClassName?: string;
|
||||
inputClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
termsClassName?: string;
|
||||
}
|
||||
|
||||
const ContactForm = ({
|
||||
title,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
useInvertedBackground,
|
||||
inputPlaceholder = "Enter your email",
|
||||
buttonText = "Sign Up",
|
||||
termsText = "By clicking Sign Up you're confirming that you agree with our Terms and Conditions.",
|
||||
onSubmit,
|
||||
centered = false,
|
||||
className = "",
|
||||
tagClassName = "",
|
||||
titleClassName = "",
|
||||
descriptionClassName = "",
|
||||
formWrapperClassName = "",
|
||||
formClassName = "",
|
||||
inputClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
termsClassName = "",
|
||||
}: ContactFormProps) => {
|
||||
const theme = useTheme();
|
||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
||||
|
||||
return (
|
||||
<div className={cls("relative z-1 flex flex-col gap-4", centered && "items-center text-center", className)}>
|
||||
<div className={cls("w-full flex flex-col gap-1", centered && "items-center")}>
|
||||
<Tag text={tag} icon={tagIcon} useInvertedBackground={useInvertedBackground} className={tagClassName} />
|
||||
|
||||
<TextAnimation
|
||||
type={theme.defaultTextAnimation as AnimationType}
|
||||
text={title}
|
||||
variant="trigger"
|
||||
className={cls("text-4xl md:text-5xl font-medium leading-[1.175] text-balance", shouldUseLightText ? "text-background" : "text-foreground", centered && "w-full md:w-8/10", titleClassName)}
|
||||
/>
|
||||
|
||||
<TextAnimation
|
||||
type={theme.defaultTextAnimation as AnimationType}
|
||||
text={description}
|
||||
variant="words-trigger"
|
||||
className={cls("text-base leading-[1.15] mb-1 text-balance", shouldUseLightText ? "text-background" : "text-foreground", centered && "w-full md:w-8/10", descriptionClassName)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={cls("w-full flex flex-col gap-1", formWrapperClassName)}>
|
||||
<EmailSignupForm
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
buttonText={buttonText}
|
||||
onSubmit={onSubmit}
|
||||
className={formClassName}
|
||||
inputClassName={inputClassName}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={buttonTextClassName}
|
||||
/>
|
||||
|
||||
<p className={cls("text-xs", shouldUseLightText ? "text-background/75" : "text-foreground/75", termsClassName)}>
|
||||
{termsText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ContactForm.displayName = "ContactForm";
|
||||
|
||||
export default memo(ContactForm);
|
||||
77
src/components/form/EmailSignupForm.tsx
Normal file
77
src/components/form/EmailSignupForm.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useState } from "react";
|
||||
import Button from "@/components/button/Button";
|
||||
import { cls } from "@/lib/utils";
|
||||
import { getButtonProps } from "@/lib/buttonUtils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
|
||||
interface EmailSignupFormProps {
|
||||
inputPlaceholder?: string;
|
||||
buttonText?: string;
|
||||
onSubmit?: (email: string) => void;
|
||||
className?: string;
|
||||
inputClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
}
|
||||
|
||||
const EmailSignupForm = memo(({
|
||||
inputPlaceholder = "Enter your email",
|
||||
buttonText = "Sign Up",
|
||||
onSubmit,
|
||||
className = "",
|
||||
inputClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
}: EmailSignupFormProps) => {
|
||||
const theme = useTheme();
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (onSubmit) {
|
||||
onSubmit(email);
|
||||
}
|
||||
};
|
||||
|
||||
const getButtonConfigProps = () => {
|
||||
if (theme.defaultButtonVariant === "hover-bubble") {
|
||||
return { bgClassName: "w-full md:w-auto" };
|
||||
}
|
||||
if (theme.defaultButtonVariant === "icon-arrow") {
|
||||
return { className: "justify-between md:justify-center" };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={cls("relative z-1 flex flex-col md:flex-row gap-3 md:gap-1 w-full card rounded-theme-capped md:rounded-theme p-1", className)}>
|
||||
<input
|
||||
type="email"
|
||||
placeholder={inputPlaceholder}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className={cls(
|
||||
"flex-1 px-4 text-base text-center md:text-left text-foreground placeholder:text-foreground/75 focus:outline-none focus:border-none truncate",
|
||||
inputClassName
|
||||
)}
|
||||
aria-label="Email address"
|
||||
/>
|
||||
<Button
|
||||
{...getButtonProps(
|
||||
{ text: buttonText, props: getButtonConfigProps() },
|
||||
0,
|
||||
theme.defaultButtonVariant,
|
||||
cls("w-full md:w-auto", buttonClassName),
|
||||
buttonTextClassName
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
});
|
||||
|
||||
EmailSignupForm.displayName = "EmailSignupForm";
|
||||
|
||||
export default EmailSignupForm;
|
||||
47
src/components/form/Input.tsx
Normal file
47
src/components/form/Input.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { cls } from "@/lib/utils";
|
||||
|
||||
interface InputProps {
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Input = ({
|
||||
type = "text",
|
||||
placeholder = "",
|
||||
value,
|
||||
onChange,
|
||||
required = false,
|
||||
disabled = false,
|
||||
ariaLabel,
|
||||
className = "",
|
||||
}: InputProps) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel || placeholder}
|
||||
className={cls(
|
||||
"relative z-1 px-4 py-3 secondary-button rounded-theme text-base text-foreground placeholder:text-foreground/75 focus:outline-none",
|
||||
disabled && "opacity-50 cursor-not-allowed",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Input.displayName = "Input";
|
||||
|
||||
export default memo(Input);
|
||||
47
src/components/form/Textarea.tsx
Normal file
47
src/components/form/Textarea.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { cls } from "@/lib/utils";
|
||||
|
||||
interface TextareaProps {
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
rows?: number;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Textarea = ({
|
||||
placeholder = "",
|
||||
value,
|
||||
onChange,
|
||||
rows = 5,
|
||||
required = false,
|
||||
disabled = false,
|
||||
ariaLabel,
|
||||
className = "",
|
||||
}: TextareaProps) => {
|
||||
return (
|
||||
<textarea
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
rows={rows}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel || placeholder}
|
||||
className={cls(
|
||||
"relative z-1 px-4 py-3 secondary-button rounded-theme-capped text-base text-foreground placeholder:text-foreground/75 focus:outline-none resize-none",
|
||||
disabled && "opacity-50 cursor-not-allowed",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export default memo(Textarea);
|
||||
99
src/components/form/WaitlistForm.tsx
Normal file
99
src/components/form/WaitlistForm.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import React, { memo, useState } from "react";
|
||||
import Input from "@/components/form/Input";
|
||||
import Button from "@/components/button/Button";
|
||||
import { cls } from "@/lib/utils";
|
||||
import { getButtonProps } from "@/lib/buttonUtils";
|
||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
||||
|
||||
interface FormField {
|
||||
name: string;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
ariaLabel?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface WaitlistFormProps {
|
||||
fields?: FormField[];
|
||||
buttonText?: string;
|
||||
onSubmit?: (data: Record<string, string>) => void;
|
||||
className?: string;
|
||||
inputsContainerClassName?: string;
|
||||
inputClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
}
|
||||
|
||||
const WaitlistForm = ({
|
||||
fields = [
|
||||
{ name: "email", type: "email", placeholder: "Your email", ariaLabel: "Email address", required: true },
|
||||
{ name: "username", type: "text", placeholder: "Telegram username", ariaLabel: "Username", required: true }
|
||||
],
|
||||
buttonText = "Join waitlist",
|
||||
onSubmit,
|
||||
className = "",
|
||||
inputsContainerClassName = "",
|
||||
inputClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
}: WaitlistFormProps) => {
|
||||
const theme = useTheme();
|
||||
const [formData, setFormData] = useState<Record<string, string>>(
|
||||
fields.reduce((acc, field) => ({ ...acc, [field.name]: "" }), {})
|
||||
);
|
||||
|
||||
const handleInputChange = (name: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (onSubmit) {
|
||||
onSubmit(formData);
|
||||
}
|
||||
};
|
||||
|
||||
const getButtonConfigProps = () => {
|
||||
if (theme.defaultButtonVariant === "hover-bubble") {
|
||||
return { bgClassName: "w-full" };
|
||||
}
|
||||
if (theme.defaultButtonVariant === "icon-arrow") {
|
||||
return { className: "justify-between" };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={cls("relative z-1 flex flex-col gap-3 w-full", className)}>
|
||||
<div className={cls("flex flex-col md:flex-row gap-3", inputsContainerClassName)}>
|
||||
{fields.map((field) => (
|
||||
<Input
|
||||
key={field.name}
|
||||
type={field.type || "text"}
|
||||
placeholder={field.placeholder || ""}
|
||||
value={formData[field.name] || ""}
|
||||
onChange={(value) => handleInputChange(field.name, value)}
|
||||
required={field.required !== false}
|
||||
ariaLabel={field.ariaLabel || field.placeholder}
|
||||
className={cls("w-full", inputClassName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
{...getButtonProps(
|
||||
{ text: buttonText, props: getButtonConfigProps() },
|
||||
0,
|
||||
theme.defaultButtonVariant,
|
||||
cls("w-full", buttonClassName),
|
||||
buttonTextClassName
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
WaitlistForm.displayName = "WaitlistForm";
|
||||
|
||||
export default memo(WaitlistForm);
|
||||
Reference in New Issue
Block a user