ギャラリー/

カルーセルギャラリー

Preview

左右ナビゲーションとサムネイル一覧を備えたカルーセル型のプロジェクトギャラリー

Source Code
tsx
178 lines
1"use client";
2
3import { useState } from "react";
4
5const projects = [
6 {
7 title: "デジタルプロダクト",
8 category: "プロダクトデザイン",
9 year: "2024",
10 description: "直感的なインターフェースを追求した、次世代のプロダクト体験。",
11 },
12 {
13 title: "ブランドリニューアル",
14 category: "ブランディング",
15 year: "2024",
16 description: "企業の本質を捉え直し、新たなビジュアル言語で再構築。",
17 },
18 {
19 title: "空間デザイン",
20 category: "インテリア",
21 year: "2023",
22 description: "光と素材の対話から生まれる、静謐で上質な空間設計。",
23 },
24 {
25 title: "エディトリアル",
26 category: "グラフィック",
27 year: "2024",
28 description: "情報の階層を丁寧に整理し、読む体験そのものをデザイン。",
29 },
30 {
31 title: "ウェブプラットフォーム",
32 category: "ウェブデザイン",
33 year: "2023",
34 description: "複雑な機能を簡潔に提示する、洗練されたインターフェース。",
35 },
36];
37
38export function GalleryCarousel001() {
39 const [current, setCurrent] = useState(0);
40
41 const goTo = (index: number) => {
42 if (index < 0) {
43 setCurrent(projects.length - 1);
44 } else if (index >= projects.length) {
45 setCurrent(0);
46 } else {
47 setCurrent(index);
48 }
49 };
50
51 return (
52 <section className="bg-background py-28 border-t border-border">
53 <div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
54 {/* ヘッダー */}
55 <div className="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between">
56 <div>
57 <p className="text-[10px] uppercase tracking-[0.3em] text-muted-foreground">
58 Gallery
59 </p>
60 <h2 className="mt-3 text-2xl font-medium tracking-wide text-foreground sm:text-3xl">
61 厳選されたプロジェクト
62 </h2>
63 </div>
64
65 {/* ナビゲーション */}
66 <div className="flex items-center gap-4">
67 <span className="text-xs tabular-nums tracking-[0.2em] text-muted-foreground/60">
68 {String(current + 1).padStart(2, "0")} / {String(projects.length).padStart(2, "0")}
69 </span>
70 <div className="flex items-center gap-2">
71 <button
72 onClick={() => goTo(current - 1)}
73 className="flex h-9 w-9 items-center justify-center rounded-full border border-border text-foreground/60 transition-colors duration-200 hover:border-foreground/40 hover:text-foreground"
74 aria-label="前のプロジェクト"
75 >
76 <svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 19l-7-7 7-7" />
78 </svg>
79 </button>
80 <button
81 onClick={() => goTo(current + 1)}
82 className="flex h-9 w-9 items-center justify-center rounded-full border border-border text-foreground/60 transition-colors duration-200 hover:border-foreground/40 hover:text-foreground"
83 aria-label="次のプロジェクト"
84 >
85 <svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
86 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 5l7 7-7 7" />
87 </svg>
88 </button>
89 </div>
90 </div>
91 </div>
92
93 <div className="mt-4 h-px bg-border/40" />
94
95 {/* カルーセル本体 */}
96 <div className="mt-12">
97 <div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
98 {/* ビジュアルエリア */}
99 <div className="relative aspect-[4/3] overflow-hidden rounded-lg border border-border bg-muted">
100 <div className="absolute inset-0 bg-gradient-to-br from-foreground/5 to-foreground/10 transition-all duration-700" />
101 {/* コーナードット */}
102 <div className="absolute left-4 top-4 h-1.5 w-1.5 rounded-full bg-foreground/20" />
103 <div className="absolute right-4 top-4 h-1.5 w-1.5 rounded-full bg-foreground/20" />
104 <div className="absolute bottom-4 left-4 h-1.5 w-1.5 rounded-full bg-foreground/20" />
105 <div className="absolute bottom-4 right-4 h-1.5 w-1.5 rounded-full bg-foreground/20" />
106 {/* 中央ラベル */}
107 <div className="absolute inset-0 flex flex-col items-center justify-center gap-3">
108 <span className="text-[10px] uppercase tracking-[0.3em] text-foreground/30">
109 {projects[current].category}
110 </span>
111 <span className="text-lg font-medium tracking-wide text-foreground/40">
112 {projects[current].title}
113 </span>
114 </div>
115 </div>
116
117 {/* テキストエリア */}
118 <div className="flex flex-col justify-between py-2">
119 <div>
120 <div className="flex items-center gap-4">
121 <span className="text-[10px] uppercase tracking-[0.2em] text-muted-foreground/50">
122 {projects[current].year}
123 </span>
124 <div className="h-px flex-1 bg-border/40" />
125 </div>
126 <h3 className="mt-6 text-xl font-medium tracking-wide text-foreground sm:text-2xl">
127 {projects[current].title}
128 </h3>
129 <p className="mt-2 text-xs uppercase tracking-[0.2em] text-muted-foreground">
130 {projects[current].category}
131 </p>
132 <p className="mt-6 text-sm font-light leading-relaxed text-muted-foreground">
133 {projects[current].description}
134 </p>
135 </div>
136
137 {/* 下部のインジケーター */}
138 <div className="mt-8 flex items-center gap-2">
139 {projects.map((_, index) => (
140 <button
141 key={index}
142 onClick={() => setCurrent(index)}
143 className={`h-px transition-all duration-300 ${
144 index === current
145 ? "w-8 bg-foreground"
146 : "w-4 bg-border/60 hover:bg-foreground/30"
147 }`}
148 aria-label={`プロジェクト ${index + 1}`}
149 />
150 ))}
151 </div>
152 </div>
153 </div>
154 </div>
155
156 {/* サムネイルリスト */}
157 <div className="mt-12 grid grid-cols-5 gap-3">
158 {projects.map((project, index) => (
159 <button
160 key={project.title}
161 onClick={() => setCurrent(index)}
162 className={`group text-left transition-opacity duration-200 ${
163 index === current ? "opacity-100" : "opacity-40 hover:opacity-70"
164 }`}
165 >
166 <div className="aspect-[3/2] overflow-hidden rounded border border-border bg-muted">
167 <div className="h-full w-full bg-gradient-to-br from-foreground/5 to-foreground/10" />
168 </div>
169 <p className="mt-2 truncate text-[10px] tracking-[0.15em] text-muted-foreground">
170 {project.title}
171 </p>
172 </button>
173 ))}
174 </div>
175 </div>
176 </section>
177 );
178}