Manifest UI
blockcontact-formsource: ok · refreshed
Contact Form
A complete contact form with name fields, phone with country selector, email, message textarea, and file attachment.
npx shadcn add "https://ui.manifest.build/r/contact-form.json"free
Where it goes
contact
Interaction
dropdown
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Registry dependencies
buttoninputlabelpopover
Files3 · source cached 10/8/2026, 2:01:37 AM
registry/form/contact-form.tsx
'use client';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { ChevronDown, Paperclip, Search, Send, X } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { countries } from './countries';
import { demoContactFormData } from './demo/form';
/**
* Data structure representing the contact form submission.
* @interface ContactFormData
* @property {string} firstName - The user's first name (required)
* @property {string} lastName - The user's last name (required)
* @property {string} [countryId] - The selected country identifier (e.g., 'us', 'fr')
* @property {string} [countryCode] - The phone country code (e.g., '+1', '+33')
* @property {string} [phoneNumber] - The user's phone number without country code
* @property {string} [email] - The user's email address
* @property {string} [message] - The message or project description
* @property {File | null} [attachment] - An optional file attachment
*/
export interface ContactFormData {
firstName?: string;
lastName?: string;
countryId?: string;
countryCode?: string;
phoneNumber?: string;
email?: string;
message?: string;
attachment?: File | null;
}
/**
* ═══════════════════════════════════════════════════════════════════════════
* ContactFormProps
* ═══════════════════════════════════════════════════════════════════════════
*
* Props for the ContactForm component with name fields, phone number with
* country selector, email input, message textarea, and file attachment support.
*/
export interface ContactFormProps {
data?: {
/** The form title displayed at the top. */
title?: string;
/** Descriptive text below the title. */
subtitle?: string;
/** Custom label for the submit button. */
submitLabel?: string;
/** Pre-filled form values. */
initialValues?: Partial<ContactFormData>;
};
actions?: {
/** Called when the form is submitted with form data. */
onSubmit?: (data: ContactFormData) => void;
};
appearance?: {
/**
* Whether to display the title section.
* @default true
*/
showTitle?: boolean;
};
control?: {
/**
* Shows loading state on submit button.
* @default false
*/
isLoading?: boolean;
};
}
/**
* A complete contact form component with name fields, phone number with country selector,
* email input, message textarea, and file attachment support.
*
* Features:
* - First and last name fields
* - Phone number with searchable country code dropdown
* - Email input with validation
* - Message textarea for project descriptions
* - File attachment with preview and removal
* - Loading state support
* - Customizable title and submit button text
*
* @component
* @example
* ```tsx
* <ContactForm
* data={{
* title: "Get in Touch",
* subtitle: "We'd love to hear from you",
* submitLabel: "Send"
* }}
* actions={{
* onSubmit: (data) => console.log("Form submitted:", data)
* }}
* appearance={{ showTitle: true }}
* control={{ isLoading: false }}
* />
* ```
*/
export function ContactForm({ data, actions, appearance, control }: ContactFormProps) {
const resolved: NonNullable<ContactFormProps['data']> = data ?? demoContactFormData
const title = resolved.title
const subtitle = resolved.subtitle
const submitLabel = resolved.submitLabel ?? 'Submit'
const initialValues = resolved.initialValues
const { onSubmit } = actions ?? {};
const { showTitle = true } = appearance ?? {};
const { isLoading = false } = control ?? {};
const fileInputRef = useRef<HTMLInputElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const [formData, setFormData] = useState<ContactFormData>({
firstName: initialValues?.firstName ?? '',
lastName: initialValues?.lastName ?? '',
countryId: initialValues?.countryId ?? 'us',
countryCode: initialValues?.countryCode ?? '+1',
phoneNumber: initialValues?.phoneNumber ?? '',
email: initialValues?.email ?? '',
message: initialValues?.message ?? '',
attachment: initialValues?.attachment ?? null,
});
const [countrySearch, setCountrySearch] = useState('');
const [countryDropdownOpen, setCountryDropdownOpen] = useState(false);
const selectedCountry = countries.find((c) => c.id === formData.countryId);
const filteredCountries = countries.filter(
(country) =>
country.name.toLowerCase().includes(countrySearch.toLowerCase()) ||
country.code.includes(countrySearch)
);
useEffect(() => {
if (countryDropdownOpen && searchInputRef.current) {
searchInputRef.current.focus();
}
}, [countryDropdownOpen]);
const handleCountrySelect = (country: (typeof countries)[0]) => {
setFormData((prev) => ({
...prev,
countryId: country.id,
countryCode: country.code,
}));
setCountryDropdownOpen(false);
setCountrySearch('');
};
const handleChange = (field: keyof ContactFormData, value: string | File | null) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] || null;
handleChange('attachment', file);
};
const handleRemoveFile = () => {
handleChange('attachment', null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit?.(formData);
};
return (
<div className="w-full bg-card rounded-xl p-6">
{showTitle && (title || subtitle) && (
<div className="mb-6">
{title && <h2 className="text-xl font-semibold text-foreground">{title}</h2>}
{subtitle && <p className="text-sm text-muted-foreground mt-1">{subtitle}</p>}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
{/* First Name & Last Name */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">First Name</Label>
<Input
id="firstName"
placeholder="John"
value={formData.firstName}
onChange={(e) => handleChange('firstName', e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Last Name</Label>
<Input
id="lastName"
placeholder="Doe"
value={formData.lastName}
onChange={(e) => handleChange('lastName', e.target.value)}
required
/>
</div>
</div>
{/* Phone Number & Email */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="phone">Phone Number</Label>
<div className="flex gap-2">
<Popover open={countryDropdownOpen} onOpenChange={setCountryDropdownOpen}>
<PopoverTrigger asChild>
<button
type="button"
className={cn(
'flex items-center gap-1.5 h-9 px-3 rounded-lg border border-input bg-transparent text-sm transition-colors',
'hover:bg-muted focus-visible:border-foreground focus-visible:outline-none'
)}
>
{selectedCountry && (
<>
<span>{selectedCountry.flag}</span>
<span>{selectedCountry.code}</span>
</>
)}
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</PopoverTrigger>
<PopoverContent className="w-[280px] p-0" align="start">
<div className="p-2 border-b border-border">
<div className="relative">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<input
ref={searchInputRef}
type="text"
placeholder="Search country..."
value={countrySearch}
onChange={(e) => setCountrySearch(e.target.value)}
className="w-full h-9 pl-9 pr-3 rounded-md border border-input bg-transparent text-sm placeholder:text-muted-foreground focus-visible:border-foreground focus-visible:outline-none"
/>
</div>
</div>
<div className="max-h-[240px] overflow-y-auto p-1">
{filteredCountries.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-4">
No country found
</p>
) : (
filteredCountries.map((country) => (
<button
key={country.id}
type="button"
onClick={() => handleCountrySelect(country)}
className={cn(
'w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-sm transition-colors text-left',
'hover:bg-muted',
formData.countryId === country.id && 'bg-muted'
)}
>
<span>{country.flag}</span>
<span className="flex-1">{country.name}</span>
<span className="text-muted-foreground">{country.code}</span>
</button>
))
)}
</div>
</PopoverContent>
</Popover>
<Input
id="phone"
type="tel"
placeholder="(555) 123-4567"
value={formData.phoneNumber}
onChange={(e) => handleChange('phoneNumber', e.target.value)}
className="flex-1"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="john@example.com"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
required
/>
</div>
</div>
{/* Message */}
<div className="space-y-2">
<Label htmlFor="message">Tell us about your project</Label>
<textarea
id="message"
placeholder="Describe your project, goals, and timeline..."
value={formData.message}
onChange={(e) => handleChange('message', e.target.value)}
rows={4}
required
className={cn(
'border-input placeholder:text-muted-foreground flex w-full rounded-lg border bg-transparent px-3 py-2 text-base transition-colors outline-none md:text-sm resize-none',
'focus-visible:border-foreground'
)}
/>
</div>
{/* Actions */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<input
ref={fileInputRef}
type="file"
onChange={handleFileChange}
className="hidden"
accept=".pdf,.doc,.docx,.txt,.png,.jpg,.jpeg"
/>
{formData.attachment ? (
<div className="flex items-center justify-center gap-2 px-3 py-2 bg-muted rounded-lg w-full sm:w-auto">
<Paperclip className="h-4 w-4 text-muted-foreground flex-shrink-0" />
<span className="text-sm text-foreground truncate max-w-[150px]">
{formData.attachment.name}
</span>
<button
type="button"
onClick={handleRemoveFile}
aria-label="Remove attachment"
className="p-1 hover:bg-background rounded transition-colors"
>
<X className="h-4 w-4 text-muted-foreground" />
</button>
</div>
) : (
<Button
type="button"
variant="outline"
size="sm"
onClick={() => fileInputRef.current?.click()}
className="w-full sm:w-auto"
>
<Paperclip className="h-4 w-4 mr-2" />
Attach a file
</Button>
)}
<Button type="submit" size="sm" disabled={isLoading} className="w-full sm:w-auto">
{isLoading ? (
'Sending...'
) : (
<>
<Send className="h-4 w-4 mr-2" />
{submitLabel}
</>
)}
</Button>
</div>
</form>
</div>
);
}
registry/form/countries.ts
export interface Country {
id: string
code: string
name: string
flag: string
}
export const countries: Country[] = [
{ id: 'af', code: '+93', name: 'Afghanistan', flag: '\u{1F1E6}\u{1F1EB}' },
{ id: 'al', code: '+355', name: 'Albania', flag: '\u{1F1E6}\u{1F1F1}' },
{ id: 'dz', code: '+213', name: 'Algeria', flag: '\u{1F1E9}\u{1F1FF}' },
{ id: 'ar', code: '+54', name: 'Argentina', flag: '\u{1F1E6}\u{1F1F7}' },
{ id: 'am', code: '+374', name: 'Armenia', flag: '\u{1F1E6}\u{1F1F2}' },
{ id: 'au', code: '+61', name: 'Australia', flag: '\u{1F1E6}\u{1F1FA}' },
{ id: 'at', code: '+43', name: 'Austria', flag: '\u{1F1E6}\u{1F1F9}' },
{ id: 'az', code: '+994', name: 'Azerbaijan', flag: '\u{1F1E6}\u{1F1FF}' },
{ id: 'bh', code: '+973', name: 'Bahrain', flag: '\u{1F1E7}\u{1F1ED}' },
{ id: 'bd', code: '+880', name: 'Bangladesh', flag: '\u{1F1E7}\u{1F1E9}' },
{ id: 'by', code: '+375', name: 'Belarus', flag: '\u{1F1E7}\u{1F1FE}' },
{ id: 'be', code: '+32', name: 'Belgium', flag: '\u{1F1E7}\u{1F1EA}' },
{ id: 'bo', code: '+591', name: 'Bolivia', flag: '\u{1F1E7}\u{1F1F4}' },
{ id: 'ba', code: '+387', name: 'Bosnia', flag: '\u{1F1E7}\u{1F1E6}' },
{ id: 'br', code: '+55', name: 'Brazil', flag: '\u{1F1E7}\u{1F1F7}' },
{ id: 'bg', code: '+359', name: 'Bulgaria', flag: '\u{1F1E7}\u{1F1EC}' },
{ id: 'kh', code: '+855', name: 'Cambodia', flag: '\u{1F1F0}\u{1F1ED}' },
{ id: 'cm', code: '+237', name: 'Cameroon', flag: '\u{1F1E8}\u{1F1F2}' },
{ id: 'ca', code: '+1', name: 'Canada', flag: '\u{1F1E8}\u{1F1E6}' },
{ id: 'cl', code: '+56', name: 'Chile', flag: '\u{1F1E8}\u{1F1F1}' },
{ id: 'cn', code: '+86', name: 'China', flag: '\u{1F1E8}\u{1F1F3}' },
{ id: 'co', code: '+57', name: 'Colombia', flag: '\u{1F1E8}\u{1F1F4}' },
{ id: 'cr', code: '+506', name: 'Costa Rica', flag: '\u{1F1E8}\u{1F1F7}' },
{ id: 'hr', code: '+385', name: 'Croatia', flag: '\u{1F1ED}\u{1F1F7}' },
{ id: 'cu', code: '+53', name: 'Cuba', flag: '\u{1F1E8}\u{1F1FA}' },
{ id: 'cy', code: '+357', name: 'Cyprus', flag: '\u{1F1E8}\u{1F1FE}' },
{ id: 'cz', code: '+420', name: 'Czech Republic', flag: '\u{1F1E8}\u{1F1FF}' },
{ id: 'dk', code: '+45', name: 'Denmark', flag: '\u{1F1E9}\u{1F1F0}' },
{ id: 'do', code: '+1', name: 'Dominican Republic', flag: '\u{1F1E9}\u{1F1F4}' },
{ id: 'ec', code: '+593', name: 'Ecuador', flag: '\u{1F1EA}\u{1F1E8}' },
{ id: 'eg', code: '+20', name: 'Egypt', flag: '\u{1F1EA}\u{1F1EC}' },
{ id: 'sv', code: '+503', name: 'El Salvador', flag: '\u{1F1F8}\u{1F1FB}' },
{ id: 'ee', code: '+372', name: 'Estonia', flag: '\u{1F1EA}\u{1F1EA}' },
{ id: 'et', code: '+251', name: 'Ethiopia', flag: '\u{1F1EA}\u{1F1F9}' },
{ id: 'fi', code: '+358', name: 'Finland', flag: '\u{1F1EB}\u{1F1EE}' },
{ id: 'fr', code: '+33', name: 'France', flag: '\u{1F1EB}\u{1F1F7}' },
{ id: 'ge', code: '+995', name: 'Georgia', flag: '\u{1F1EC}\u{1F1EA}' },
{ id: 'de', code: '+49', name: 'Germany', flag: '\u{1F1E9}\u{1F1EA}' },
{ id: 'gh', code: '+233', name: 'Ghana', flag: '\u{1F1EC}\u{1F1ED}' },
{ id: 'gr', code: '+30', name: 'Greece', flag: '\u{1F1EC}\u{1F1F7}' },
{ id: 'gt', code: '+502', name: 'Guatemala', flag: '\u{1F1EC}\u{1F1F9}' },
{ id: 'hn', code: '+504', name: 'Honduras', flag: '\u{1F1ED}\u{1F1F3}' },
{ id: 'hk', code: '+852', name: 'Hong Kong', flag: '\u{1F1ED}\u{1F1F0}' },
{ id: 'hu', code: '+36', name: 'Hungary', flag: '\u{1F1ED}\u{1F1FA}' },
{ id: 'is', code: '+354', name: 'Iceland', flag: '\u{1F1EE}\u{1F1F8}' },
{ id: 'in', code: '+91', name: 'India', flag: '\u{1F1EE}\u{1F1F3}' },
{ id: 'id', code: '+62', name: 'Indonesia', flag: '\u{1F1EE}\u{1F1E9}' },
{ id: 'ir', code: '+98', name: 'Iran', flag: '\u{1F1EE}\u{1F1F7}' },
{ id: 'iq', code: '+964', name: 'Iraq', flag: '\u{1F1EE}\u{1F1F6}' },
{ id: 'ie', code: '+353', name: 'Ireland', flag: '\u{1F1EE}\u{1F1EA}' },
{ id: 'il', code: '+972', name: 'Israel', flag: '\u{1F1EE}\u{1F1F1}' },
{ id: 'it', code: '+39', name: 'Italy', flag: '\u{1F1EE}\u{1F1F9}' },
{ id: 'jm', code: '+1', name: 'Jamaica', flag: '\u{1F1EF}\u{1F1F2}' },
{ id: 'jp', code: '+81', name: 'Japan', flag: '\u{1F1EF}\u{1F1F5}' },
{ id: 'jo', code: '+962', name: 'Jordan', flag: '\u{1F1EF}\u{1F1F4}' },
{ id: 'kz', code: '+7', name: 'Kazakhstan', flag: '\u{1F1F0}\u{1F1FF}' },
{ id: 'ke', code: '+254', name: 'Kenya', flag: '\u{1F1F0}\u{1F1EA}' },
{ id: 'kw', code: '+965', name: 'Kuwait', flag: '\u{1F1F0}\u{1F1FC}' },
{ id: 'lv', code: '+371', name: 'Latvia', flag: '\u{1F1F1}\u{1F1FB}' },
{ id: 'lb', code: '+961', name: 'Lebanon', flag: '\u{1F1F1}\u{1F1E7}' },
{ id: 'lt', code: '+370', name: 'Lithuania', flag: '\u{1F1F1}\u{1F1F9}' },
{ id: 'lu', code: '+352', name: 'Luxembourg', flag: '\u{1F1F1}\u{1F1FA}' },
{ id: 'my', code: '+60', name: 'Malaysia', flag: '\u{1F1F2}\u{1F1FE}' },
{ id: 'mx', code: '+52', name: 'Mexico', flag: '\u{1F1F2}\u{1F1FD}' },
{ id: 'ma', code: '+212', name: 'Morocco', flag: '\u{1F1F2}\u{1F1E6}' },
{ id: 'mm', code: '+95', name: 'Myanmar', flag: '\u{1F1F2}\u{1F1F2}' },
{ id: 'np', code: '+977', name: 'Nepal', flag: '\u{1F1F3}\u{1F1F5}' },
{ id: 'nl', code: '+31', name: 'Netherlands', flag: '\u{1F1F3}\u{1F1F1}' },
{ id: 'nz', code: '+64', name: 'New Zealand', flag: '\u{1F1F3}\u{1F1FF}' },
{ id: 'ng', code: '+234', name: 'Nigeria', flag: '\u{1F1F3}\u{1F1EC}' },
{ id: 'no', code: '+47', name: 'Norway', flag: '\u{1F1F3}\u{1F1F4}' },
{ id: 'om', code: '+968', name: 'Oman', flag: '\u{1F1F4}\u{1F1F2}' },
{ id: 'pk', code: '+92', name: 'Pakistan', flag: '\u{1F1F5}\u{1F1F0}' },
{ id: 'pa', code: '+507', name: 'Panama', flag: '\u{1F1F5}\u{1F1E6}' },
{ id: 'py', code: '+595', name: 'Paraguay', flag: '\u{1F1F5}\u{1F1FE}' },
{ id: 'pe', code: '+51', name: 'Peru', flag: '\u{1F1F5}\u{1F1EA}' },
{ id: 'ph', code: '+63', name: 'Philippines', flag: '\u{1F1F5}\u{1F1ED}' },
{ id: 'pl', code: '+48', name: 'Poland', flag: '\u{1F1F5}\u{1F1F1}' },
{ id: 'pt', code: '+351', name: 'Portugal', flag: '\u{1F1F5}\u{1F1F9}' },
{ id: 'pr', code: '+1', name: 'Puerto Rico', flag: '\u{1F1F5}\u{1F1F7}' },
{ id: 'qa', code: '+974', name: 'Qatar', flag: '\u{1F1F6}\u{1F1E6}' },
{ id: 'ro', code: '+40', name: 'Romania', flag: '\u{1F1F7}\u{1F1F4}' },
{ id: 'ru', code: '+7', name: 'Russia', flag: '\u{1F1F7}\u{1F1FA}' },
{ id: 'sa', code: '+966', name: 'Saudi Arabia', flag: '\u{1F1F8}\u{1F1E6}' },
{ id: 'sn', code: '+221', name: 'Senegal', flag: '\u{1F1F8}\u{1F1F3}' },
{ id: 'rs', code: '+381', name: 'Serbia', flag: '\u{1F1F7}\u{1F1F8}' },
{ id: 'sg', code: '+65', name: 'Singapore', flag: '\u{1F1F8}\u{1F1EC}' },
{ id: 'sk', code: '+421', name: 'Slovakia', flag: '\u{1F1F8}\u{1F1F0}' },
{ id: 'si', code: '+386', name: 'Slovenia', flag: '\u{1F1F8}\u{1F1EE}' },
{ id: 'za', code: '+27', name: 'South Africa', flag: '\u{1F1FF}\u{1F1E6}' },
{ id: 'kr', code: '+82', name: 'South Korea', flag: '\u{1F1F0}\u{1F1F7}' },
{ id: 'es', code: '+34', name: 'Spain', flag: '\u{1F1EA}\u{1F1F8}' },
{ id: 'lk', code: '+94', name: 'Sri Lanka', flag: '\u{1F1F1}\u{1F1F0}' },
{ id: 'se', code: '+46', name: 'Sweden', flag: '\u{1F1F8}\u{1F1EA}' },
{ id: 'ch', code: '+41', name: 'Switzerland', flag: '\u{1F1E8}\u{1F1ED}' },
{ id: 'tw', code: '+886', name: 'Taiwan', flag: '\u{1F1F9}\u{1F1FC}' },
{ id: 'th', code: '+66', name: 'Thailand', flag: '\u{1F1F9}\u{1F1ED}' },
{ id: 'tn', code: '+216', name: 'Tunisia', flag: '\u{1F1F9}\u{1F1F3}' },
{ id: 'tr', code: '+90', name: 'Turkey', flag: '\u{1F1F9}\u{1F1F7}' },
{ id: 'ua', code: '+380', name: 'Ukraine', flag: '\u{1F1FA}\u{1F1E6}' },
{ id: 'ae', code: '+971', name: 'United Arab Emirates', flag: '\u{1F1E6}\u{1F1EA}' },
{ id: 'gb', code: '+44', name: 'United Kingdom', flag: '\u{1F1EC}\u{1F1E7}' },
{ id: 'us', code: '+1', name: 'United States', flag: '\u{1F1FA}\u{1F1F8}' },
{ id: 'uy', code: '+598', name: 'Uruguay', flag: '\u{1F1FA}\u{1F1FE}' },
{ id: 'uz', code: '+998', name: 'Uzbekistan', flag: '\u{1F1FA}\u{1F1FF}' },
{ id: 've', code: '+58', name: 'Venezuela', flag: '\u{1F1FB}\u{1F1EA}' },
{ id: 'vn', code: '+84', name: 'Vietnam', flag: '\u{1F1FB}\u{1F1F3}' },
{ id: 'ye', code: '+967', name: 'Yemen', flag: '\u{1F1FE}\u{1F1EA}' },
{ id: 'zm', code: '+260', name: 'Zambia', flag: '\u{1F1FF}\u{1F1F2}' },
{ id: 'zw', code: '+263', name: 'Zimbabwe', flag: '\u{1F1FF}\u{1F1FC}' },
]
registry/form/demo/form.ts
// Demo data for Form category components
// This file contains sample data used for component previews and documentation
/**
* Generate available dates dynamically so the DateTimePicker preview
* always has clickable dates regardless of when it is viewed.
*/
function generateAvailableDates(): Date[] {
const dates: Date[] = [];
const now = new Date();
// Walk through current and next month, only keeping weekdays (Mon-Fri)
for (let m = 0; m <= 1; m++) {
for (let d = 1; d <= 28; d += 2) {
const date = new Date(now.getFullYear(), now.getMonth() + m, d);
const day = date.getDay();
if (day !== 0 && day !== 6) {
dates.push(date);
}
}
}
return dates;
}
export const demoContactFormData = {
title: 'Get in Touch',
subtitle: "We'd love to hear from you. Fill out the form below.",
submitLabel: 'Send Message',
}
export const demoIssueReportFormData = {
title: 'Report an Issue',
teams: ['Engineering', 'Product', 'Design', 'Marketing', 'Operations'],
locations: ['New York - HQ', 'San Francisco', 'London', 'Remote'],
categories: {
Software: ['Business App', 'Email', 'VPN', 'Browser', 'OS'],
Hardware: ['Computer', 'Monitor', 'Keyboard', 'Mouse', 'Printer'],
Network: ['Wi-Fi', 'Ethernet', 'VPN Access'],
Access: ['Account', 'Permissions', 'Password Reset'],
} as Record<string, string[]>,
impacts: [
{ value: 'critical', label: 'Critical - Work stopped' },
{ value: 'high', label: 'High - Major feature broken' },
{ value: 'medium', label: 'Medium - Workaround available' },
{ value: 'low', label: 'Low - Minor inconvenience' },
],
urgencies: [
{ value: 'immediate', label: 'Immediate' },
{ value: 'today', label: 'Today' },
{ value: 'this-week', label: 'This week' },
{ value: 'no-rush', label: 'No rush' },
],
frequencies: [
{ value: 'constant', label: 'Constant' },
{ value: 'frequent', label: 'Frequent' },
{ value: 'occasional', label: 'Occasional' },
{ value: 'once', label: 'Happened once' },
],
attemptedActions: [
'Restarted the application',
'Cleared browser cache',
'Restarted computer',
'Checked internet connection',
'Contacted a colleague',
],
}
export const demoDateTimePickerData = {
title: 'Select a Date & Time',
availableDates: generateAvailableDates(),
availableTimeSlots: [
'9:00am',
'10:00am',
'11:30am',
'1:00pm',
'2:30pm',
'4:00pm',
],
timezone: 'Eastern Time - US & Canada',
};