mishig HF Staff commited on
Commit
08cc322
·
unverified ·
2 Parent(s): 20ccb48edcd057

Merge pull request #30 from huggingface/stylize_home_page

Browse files

feat(home): enhance UI with fade-in animation and loading state for dataset search

Files changed (2) hide show
  1. src/app/globals.css +15 -0
  2. src/app/page.tsx +133 -39
src/app/globals.css CHANGED
@@ -49,3 +49,18 @@ body {
49
  width: 177.78vh;
50
  }
51
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  width: 177.78vh;
50
  }
51
  }
52
+
53
+ @keyframes fadeInUp {
54
+ from {
55
+ opacity: 0;
56
+ transform: translateY(20px);
57
+ }
58
+ to {
59
+ opacity: 1;
60
+ transform: translateY(0);
61
+ }
62
+ }
63
+
64
+ .animate-fade-in-up {
65
+ animation: fadeInUp 0.6s ease-out forwards;
66
+ }
src/app/page.tsx CHANGED
@@ -24,6 +24,12 @@ export default function Home() {
24
  );
25
  }
26
 
 
 
 
 
 
 
27
  function HomeInner() {
28
  const searchParams = useSearchParams();
29
  const router = useRouter();
@@ -123,14 +129,21 @@ function HomeInner() {
123
  const [suggestions, setSuggestions] = useState<string[]>([]);
124
  const [showSuggestions, setShowSuggestions] = useState(false);
125
  const [activeIndex, setActiveIndex] = useState(-1);
 
 
126
  const containerRef = useRef<HTMLDivElement>(null);
127
 
128
  useEffect(() => {
129
  if (!query.trim()) {
130
  setSuggestions([]);
131
  setShowSuggestions(false);
 
 
132
  return;
133
  }
 
 
 
134
  const timer = setTimeout(async () => {
135
  try {
136
  const res = await fetch(
@@ -142,10 +155,12 @@ function HomeInner() {
142
  (data.datasets as { id: string }[] | undefined) ?? []
143
  ).map((d) => d.id);
144
  setSuggestions(ids);
145
- setShowSuggestions(ids.length > 0);
146
  setActiveIndex(-1);
147
  } catch {
148
  setSuggestions([]);
 
 
 
149
  }
150
  }, 150);
151
  return () => clearTimeout(timer);
@@ -201,72 +216,136 @@ function HomeInner() {
201
  <div className="video-background">
202
  <div id="yt-bg-player" />
203
  </div>
204
- {/* Overlay */}
205
- <div className="fixed top-0 right-0 bottom-0 left-0 bg-black/60 -z-0" />
 
 
206
  {/* Centered Content */}
207
- <div className="relative z-10 h-screen flex flex-col items-center justify-center text-white text-center">
208
- <h1 className="text-4xl md:text-5xl font-bold mb-6 drop-shadow-lg">
209
- LeRobot Dataset Tool and Visualizer
 
 
 
 
 
210
  </h1>
211
- <form
212
- onSubmit={handleSubmit}
213
- className="flex gap-2 justify-center mt-6"
214
- >
 
 
 
 
215
  <div ref={containerRef} className="relative">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  <input
217
  type="text"
218
  value={query}
219
  onChange={(e) => setQuery(e.target.value)}
220
  onKeyDown={handleKeyDown}
221
- onFocus={() => suggestions.length > 0 && setShowSuggestions(true)}
222
  placeholder="Enter dataset id (e.g. lerobot/pusht)"
223
- className="px-4 py-2.5 rounded-md text-base text-white bg-white/10 backdrop-blur-sm border border-white/40 focus:outline-none focus:border-sky-400 focus:bg-white/15 w-[380px] shadow-md placeholder:text-white/50 transition-colors"
224
  autoComplete="off"
225
  />
 
 
226
  {showSuggestions && (
227
  <ul className="absolute left-0 right-0 top-full mt-1 rounded-md bg-slate-900/95 backdrop-blur-sm border border-white/10 shadow-xl overflow-hidden z-50 max-h-64 overflow-y-auto">
228
- {suggestions.map((id, i) => (
229
- <li key={id}>
230
- <button
231
- type="button"
232
- className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${
233
- i === activeIndex
234
- ? "bg-sky-600 text-white"
235
- : "text-slate-200 hover:bg-slate-700"
236
- }`}
237
- onMouseDown={(e) => {
238
- e.preventDefault();
239
- navigate(id);
240
- }}
241
- onMouseEnter={() => setActiveIndex(i)}
242
  >
243
- {id}
244
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  </li>
246
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  </ul>
248
  )}
249
  </div>
 
250
  <button
251
  type="submit"
252
- className="px-5 py-2.5 rounded-md bg-sky-400 text-black font-semibold text-base hover:bg-sky-300 transition-colors shadow-md"
253
  >
254
  Go
 
 
 
255
  </button>
256
  </form>
 
257
  {/* Example Datasets */}
258
  <div className="mt-8">
259
- <div className="font-semibold mb-2 text-lg">Example Datasets:</div>
260
- <div className="flex flex-col gap-2 items-center">
261
- {[
262
- "lerobot-data-collection/level12_rac_2_2026-02-07",
263
- "imstevenpmwork/thanos_picking_power_gem",
264
- "lerobot/aloha_static_cups_open",
265
- ].map((ds) => (
266
  <button
267
  key={ds}
268
  type="button"
269
- className="px-4 py-2 rounded bg-slate-700 text-sky-200 hover:bg-sky-700 hover:text-white transition-colors shadow"
270
  onClick={() => navigate(ds)}
271
  >
272
  {ds}
@@ -275,11 +354,26 @@ function HomeInner() {
275
  </div>
276
  </div>
277
 
 
278
  <Link
279
  href="/explore"
280
- className="inline-block px-6 py-3 mt-8 rounded-md bg-sky-500 text-white font-semibold text-lg shadow-lg hover:bg-sky-400 transition-colors"
281
  >
282
  Explore Open Datasets
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  </Link>
284
  </div>
285
  </div>
 
24
  );
25
  }
26
 
27
+ const EXAMPLE_DATASETS = [
28
+ "lerobot-data-collection/level12_rac_2_2026-02-07",
29
+ "imstevenpmwork/thanos_picking_power_gem",
30
+ "lerobot/aloha_static_cups_open",
31
+ ];
32
+
33
  function HomeInner() {
34
  const searchParams = useSearchParams();
35
  const router = useRouter();
 
129
  const [suggestions, setSuggestions] = useState<string[]>([]);
130
  const [showSuggestions, setShowSuggestions] = useState(false);
131
  const [activeIndex, setActiveIndex] = useState(-1);
132
+ const [isLoading, setIsLoading] = useState(false);
133
+ const [hasFetched, setHasFetched] = useState(false);
134
  const containerRef = useRef<HTMLDivElement>(null);
135
 
136
  useEffect(() => {
137
  if (!query.trim()) {
138
  setSuggestions([]);
139
  setShowSuggestions(false);
140
+ setIsLoading(false);
141
+ setHasFetched(false);
142
  return;
143
  }
144
+ setIsLoading(true);
145
+ setHasFetched(false);
146
+ setShowSuggestions(true);
147
  const timer = setTimeout(async () => {
148
  try {
149
  const res = await fetch(
 
155
  (data.datasets as { id: string }[] | undefined) ?? []
156
  ).map((d) => d.id);
157
  setSuggestions(ids);
 
158
  setActiveIndex(-1);
159
  } catch {
160
  setSuggestions([]);
161
+ } finally {
162
+ setIsLoading(false);
163
+ setHasFetched(true);
164
  }
165
  }, 150);
166
  return () => clearTimeout(timer);
 
216
  <div className="video-background">
217
  <div id="yt-bg-player" />
218
  </div>
219
+
220
+ {/* Gradient overlay darker at edges, lighter at center for depth */}
221
+ <div className="fixed inset-0 -z-0 bg-[radial-gradient(ellipse_at_center,rgba(0,0,0,0.35)_0%,rgba(0,0,0,0.80)_100%)]" />
222
+
223
  {/* Centered Content */}
224
+ <div className="relative z-10 h-screen flex flex-col items-center justify-center text-white text-center animate-fade-in-up px-4">
225
+ {/* Title */}
226
+ <h1 className="text-4xl md:text-5xl font-bold mb-2 drop-shadow-lg tracking-tight">
227
+ LeRobot{" "}
228
+ <span className="bg-gradient-to-r from-sky-400 to-indigo-400 bg-clip-text text-transparent">
229
+ Dataset
230
+ </span>{" "}
231
+ Visualizer
232
  </h1>
233
+
234
+ {/* Subtitle */}
235
+ <p className="text-white/55 text-base md:text-lg mb-8 max-w-md">
236
+ Explore and visualize robot learning datasets from Hugging Face
237
+ </p>
238
+
239
+ {/* Search form */}
240
+ <form onSubmit={handleSubmit} className="flex gap-2 justify-center">
241
  <div ref={containerRef} className="relative">
242
+ {/* Search icon */}
243
+ <svg
244
+ className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/40 pointer-events-none"
245
+ xmlns="http://www.w3.org/2000/svg"
246
+ fill="none"
247
+ viewBox="0 0 24 24"
248
+ stroke="currentColor"
249
+ strokeWidth={2}
250
+ >
251
+ <path
252
+ strokeLinecap="round"
253
+ strokeLinejoin="round"
254
+ d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"
255
+ />
256
+ </svg>
257
+
258
  <input
259
  type="text"
260
  value={query}
261
  onChange={(e) => setQuery(e.target.value)}
262
  onKeyDown={handleKeyDown}
263
+ onFocus={() => query.trim() && setShowSuggestions(true)}
264
  placeholder="Enter dataset id (e.g. lerobot/pusht)"
265
+ className="pl-10 pr-4 py-2.5 rounded-md text-base text-white bg-white/10 backdrop-blur-sm border border-white/30 focus:outline-none focus:border-sky-400 focus:bg-white/15 w-[380px] shadow-md placeholder:text-white/40 transition-colors"
266
  autoComplete="off"
267
  />
268
+
269
+ {/* Suggestions dropdown */}
270
  {showSuggestions && (
271
  <ul className="absolute left-0 right-0 top-full mt-1 rounded-md bg-slate-900/95 backdrop-blur-sm border border-white/10 shadow-xl overflow-hidden z-50 max-h-64 overflow-y-auto">
272
+ {isLoading ? (
273
+ <li className="flex items-center gap-2.5 px-4 py-3 text-sm text-white/50">
274
+ <svg
275
+ className="animate-spin w-4 h-4 shrink-0"
276
+ xmlns="http://www.w3.org/2000/svg"
277
+ fill="none"
278
+ viewBox="0 0 24 24"
 
 
 
 
 
 
 
279
  >
280
+ <circle
281
+ className="opacity-25"
282
+ cx="12"
283
+ cy="12"
284
+ r="10"
285
+ stroke="currentColor"
286
+ strokeWidth="4"
287
+ />
288
+ <path
289
+ className="opacity-75"
290
+ fill="currentColor"
291
+ d="M4 12a8 8 0 018-8v8H4z"
292
+ />
293
+ </svg>
294
+ Searching…
295
  </li>
296
+ ) : suggestions.length > 0 ? (
297
+ suggestions.map((id, i) => (
298
+ <li key={id}>
299
+ <button
300
+ type="button"
301
+ className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${
302
+ i === activeIndex
303
+ ? "bg-sky-600 text-white"
304
+ : "text-slate-200 hover:bg-slate-700"
305
+ }`}
306
+ onMouseDown={(e) => {
307
+ e.preventDefault();
308
+ navigate(id);
309
+ }}
310
+ onMouseEnter={() => setActiveIndex(i)}
311
+ >
312
+ {id}
313
+ </button>
314
+ </li>
315
+ ))
316
+ ) : (
317
+ hasFetched && (
318
+ <li className="px-4 py-3 text-sm text-white/40">
319
+ No datasets found
320
+ </li>
321
+ )
322
+ )}
323
  </ul>
324
  )}
325
  </div>
326
+
327
  <button
328
  type="submit"
329
+ className="px-5 py-2.5 rounded-md bg-sky-500 text-white font-semibold text-base hover:bg-sky-400 active:scale-95 transition-all shadow-md flex items-center gap-2"
330
  >
331
  Go
332
+ <kbd className="text-xs font-mono bg-white/20 rounded px-1 py-0.5 leading-tight">
333
+
334
+ </kbd>
335
  </button>
336
  </form>
337
+
338
  {/* Example Datasets */}
339
  <div className="mt-8">
340
+ <p className="text-white/40 text-xs uppercase tracking-widest mb-3 font-medium">
341
+ Example Datasets
342
+ </p>
343
+ <div className="flex flex-row flex-wrap gap-2 justify-center max-w-xl">
344
+ {EXAMPLE_DATASETS.map((ds) => (
 
 
345
  <button
346
  key={ds}
347
  type="button"
348
+ className="px-3 py-1.5 rounded-full border border-white/20 text-sm text-sky-200/80 hover:border-sky-400 hover:text-white hover:bg-sky-500/15 active:scale-95 transition-all backdrop-blur-sm"
349
  onClick={() => navigate(ds)}
350
  >
351
  {ds}
 
354
  </div>
355
  </div>
356
 
357
+ {/* Explore CTA */}
358
  <Link
359
  href="/explore"
360
+ className="inline-flex items-center gap-2 px-6 py-3 mt-8 rounded-md bg-sky-500/90 backdrop-blur-sm text-white font-semibold text-lg shadow-lg hover:bg-sky-400 active:scale-95 transition-all"
361
  >
362
  Explore Open Datasets
363
+ <svg
364
+ xmlns="http://www.w3.org/2000/svg"
365
+ className="w-5 h-5"
366
+ fill="none"
367
+ viewBox="0 0 24 24"
368
+ stroke="currentColor"
369
+ strokeWidth={2}
370
+ >
371
+ <path
372
+ strokeLinecap="round"
373
+ strokeLinejoin="round"
374
+ d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
375
+ />
376
+ </svg>
377
  </Link>
378
  </div>
379
  </div>