コンタクト/

タブ切り替えコンタクト

Preview

フォーム・オフィス情報・ソーシャルリンクをタブで切り替える多機能コンタクトセクション

Source Code
tsx
262 lines
1"use client";
2
3import { useState } from "react";
4
5const tabs = [
6 { id: "form", label: "フォーム" },
7 { id: "office", label: "オフィス" },
8 { id: "social", label: "ソーシャル" },
9] as const;
10
11type TabId = (typeof tabs)[number]["id"];
12
13export function ContactTabbed001() {
14 const [activeTab, setActiveTab] = useState<TabId>("form");
15 const [formData, setFormData] = useState({
16 name: "",
17 email: "",
18 message: "",
19 });
20
21 const handleSubmit = (e: React.FormEvent) => {
22 e.preventDefault();
23 };
24
25 const handleChange = (
26 e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
27 ) => {
28 setFormData((prev) => ({
29 ...prev,
30 [e.target.name]: e.target.value,
31 }));
32 };
33
34 return (
35 <section className="border-t border-border bg-background py-28">
36 <div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
37 {/* Header */}
38 <div className="text-center">
39 <p className="text-[10px] uppercase tracking-[0.3em] text-muted-foreground">
40 Contact
41 </p>
42 <h2 className="mt-3 text-2xl font-medium tracking-wide text-foreground sm:text-3xl">
43 お問い合わせ
44 </h2>
45 <p className="mx-auto mt-5 max-w-md text-sm font-light leading-relaxed text-muted-foreground">
46 ご質問やご相談がございましたら、お気軽にご連絡ください。
47 </p>
48 </div>
49
50 {/* Tabs */}
51 <div className="mt-14 flex justify-center">
52 <div className="inline-flex items-center gap-1 rounded-full border border-border/60 p-1">
53 {tabs.map((tab) => (
54 <button
55 key={tab.id}
56 onClick={() => setActiveTab(tab.id)}
57 className={`rounded-full px-5 py-1.5 text-xs tracking-[0.15em] uppercase transition-all duration-200 ${
58 activeTab === tab.id
59 ? "bg-foreground text-background"
60 : "text-muted-foreground hover:text-foreground"
61 }`}
62 >
63 {tab.label}
64 </button>
65 ))}
66 </div>
67 </div>
68
69 {/* Tab Content */}
70 <div className="mt-12">
71 {/* Form Tab */}
72 {activeTab === "form" && (
73 <form onSubmit={handleSubmit} className="space-y-8">
74 <div className="grid grid-cols-1 gap-8 sm:grid-cols-2">
75 <div>
76 <label
77 htmlFor="contact-tabbed-name"
78 className="block text-[10px] uppercase tracking-[0.2em] text-muted-foreground"
79 >
80 お名前
81 </label>
82 <input
83 type="text"
84 id="contact-tabbed-name"
85 name="name"
86 value={formData.name}
87 onChange={handleChange}
88 className="mt-3 block w-full border-b border-border bg-transparent py-3 text-sm font-light tracking-wide text-foreground placeholder-muted-foreground/40 transition-colors duration-200 focus:border-foreground focus:outline-none"
89 placeholder="山田太郎"
90 />
91 </div>
92 <div>
93 <label
94 htmlFor="contact-tabbed-email"
95 className="block text-[10px] uppercase tracking-[0.2em] text-muted-foreground"
96 >
97 メールアドレス
98 </label>
99 <input
100 type="email"
101 id="contact-tabbed-email"
102 name="email"
103 value={formData.email}
104 onChange={handleChange}
105 className="mt-3 block w-full border-b border-border bg-transparent py-3 text-sm font-light tracking-wide text-foreground placeholder-muted-foreground/40 transition-colors duration-200 focus:border-foreground focus:outline-none"
106 placeholder="your@email.com"
107 />
108 </div>
109 </div>
110 <div>
111 <label
112 htmlFor="contact-tabbed-message"
113 className="block text-[10px] uppercase tracking-[0.2em] text-muted-foreground"
114 >
115 メッセージ
116 </label>
117 <textarea
118 id="contact-tabbed-message"
119 name="message"
120 rows={4}
121 value={formData.message}
122 onChange={handleChange}
123 className="mt-3 block w-full resize-none border-b border-border bg-transparent py-3 text-sm font-light tracking-wide text-foreground placeholder-muted-foreground/40 transition-colors duration-200 focus:border-foreground focus:outline-none"
124 placeholder="ご要件をお聞かせください..."
125 />
126 </div>
127 <div className="pt-2">
128 <button
129 type="submit"
130 className="group inline-flex items-center gap-3 text-sm font-medium tracking-wide text-foreground transition-colors duration-200 hover:text-muted-foreground"
131 >
132 送信する
133 <svg
134 className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-1"
135 fill="none"
136 stroke="currentColor"
137 viewBox="0 0 24 24"
138 >
139 <path
140 strokeLinecap="round"
141 strokeLinejoin="round"
142 strokeWidth={1.5}
143 d="M17 8l4 4m0 0l-4 4m4-4H3"
144 />
145 </svg>
146 </button>
147 </div>
148 </form>
149 )}
150
151 {/* Office Tab */}
152 {activeTab === "office" && (
153 <div className="grid grid-cols-1 gap-12 sm:grid-cols-2">
154 {[
155 {
156 city: "東京",
157 address: "東京都渋谷区神宮前 3-21-5",
158 phone: "+81 3-0000-0000",
159 hours: "月〜金 9:00 - 18:00",
160 },
161 {
162 city: "大阪",
163 address: "大阪府大阪市北区梅田 1-12-8",
164 phone: "+81 6-0000-0000",
165 hours: "月〜金 9:00 - 18:00",
166 },
167 ].map((office) => (
168 <div key={office.city}>
169 <div className="flex items-center gap-2.5">
170 <span className="h-1.5 w-1.5 rounded-full bg-foreground/20" />
171 <h3 className="text-sm font-medium tracking-wide text-foreground">
172 {office.city}
173 </h3>
174 </div>
175 <div className="ml-4 mt-5 space-y-4">
176 <div>
177 <p className="text-[10px] uppercase tracking-[0.2em] text-muted-foreground/60">
178 住所
179 </p>
180 <p className="mt-1.5 text-sm font-light tracking-wide text-foreground">
181 {office.address}
182 </p>
183 </div>
184 <div>
185 <p className="text-[10px] uppercase tracking-[0.2em] text-muted-foreground/60">
186 電話
187 </p>
188 <p className="mt-1.5 text-sm font-light tracking-wide text-foreground">
189 {office.phone}
190 </p>
191 </div>
192 <div>
193 <p className="text-[10px] uppercase tracking-[0.2em] text-muted-foreground/60">
194 営業時間
195 </p>
196 <p className="mt-1.5 text-sm font-light tracking-wide text-foreground">
197 {office.hours}
198 </p>
199 </div>
200 </div>
201 </div>
202 ))}
203 </div>
204 )}
205
206 {/* Social Tab */}
207 {activeTab === "social" && (
208 <div className="space-y-6">
209 {[
210 {
211 platform: "X (Twitter)",
212 handle: "@example_studio",
213 description: "最新情報やデザインに関する投稿",
214 },
215 {
216 platform: "GitHub",
217 handle: "example-studio",
218 description: "オープンソースプロジェクト",
219 },
220 {
221 platform: "LinkedIn",
222 handle: "Example Studio",
223 description: "採用情報とカンパニーニュース",
224 },
225 {
226 platform: "Dribbble",
227 handle: "example_studio",
228 description: "デザインワークのポートフォリオ",
229 },
230 ].map((social, index) => (
231 <div key={social.platform}>
232 {index > 0 && <div className="mb-6 h-px bg-border/40" />}
233 <div className="flex items-start justify-between gap-4">
234 <div>
235 <p className="text-sm font-medium tracking-wide text-foreground">
236 {social.platform}
237 </p>
238 <p className="mt-1 text-xs font-light tracking-wide text-muted-foreground">
239 {social.description}
240 </p>
241 </div>
242 <span className="shrink-0 text-sm font-light tracking-wide text-muted-foreground/60">
243 {social.handle}
244 </span>
245 </div>
246 </div>
247 ))}
248 </div>
249 )}
250 </div>
251
252 {/* Bottom note */}
253 <div className="mt-16 text-center">
254 <div className="mx-auto h-px w-12 bg-border/40" />
255 <p className="mt-6 text-xs font-light tracking-wide text-muted-foreground/60">
256 通常2営業日以内にご返信いたします
257 </p>
258 </div>
259 </div>
260 </section>
261 );
262}