Next.jsTailwind CSSShadcn UI+1 więcej

Tworzymy UI - własny Spotify: Odtwarzacz Muzyki w Next.js

Praktyczne ćwiczenie z budowania interfejsów. Wykorzystamy komponenty Shadcn UI i magię Tailwinda, aby stworzyć responsywną kartę odtwarzacza muzyki.

/
5 min czytania
Tworzymy UI - własny Spotify: Odtwarzacz Muzyki w Next.js
Nowoczesny interfejs odtwarzacza zbudowany z gotowych komponentów

Witajcie! 👋

Ostatnio poznaliśmy moim zdaniem trzy najważniejsze technologie nowoczesnego frontendu: Next.js, Tailwind CSS i shadcn/ui. Wiemy już, jak skonfigurować środowisko w chmurze.

Dzisiaj przechodzimy do praktyki. Zamiast nudnych formularzy, zbudujemy coś, co każdy z nas ma w kieszeni – Odtwarzacz Muzyki.

Dlaczego to ćwiczenie jest świetne na start?

  1. Świetny element portfolio: Niewielkim nakładem pracy stworzymy coś, co wygląda profesjonalnie.
  2. Praca z układem: Przećwiczymy Flexboxa wewnątrz komponentów.
  3. Hierarchia wizualna: Nauczymy się sterować uwagą użytkownika za pomocą rozmiaru tekstu i elementów (możesz później sam dodać kolory).

🛠️ Krok 1: Przygotowanie "Klocków"

Na początku musimy założyć konto w serwisie: stackblitz.com

Pamiętacie, że shadcn nie jest biblioteką, którą instalujemy w całości? Pobieramy tylko to, czego potrzebujemy.

W naszym odtwarzaczu użyjemy trzech elementów:

  • Card (Karta) – jako nasz główny kontener.
  • Button (Przycisk) – do sterowania (Play, Pause, Serduszko).
  • Slider (Suwak) – do paska postępu utworu.

Otwórzcie terminal w swoim projekcie (CodeSandbox/StackBlitz) i wpiszcie:

npx shadcn@latest add card button slider

Ikony (Heart, Play, StepBack itp.) mamy dostępne "z pudełka" dzięki bibliotece lucide-react, która instaluje się automatycznie z shadcn.


🧬 Krok 2: Tworzymy strukturę pliku

Zbudujemy nasz komponent w pliku app/page.tsx. Poniżej znajduje się kompletny kod struktury pliku.

import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from "@/components/ui/card"
import { Heart, StepBack, Play, StepForward, ListMusic } from "lucide-react"
import { Slider } from "@/components/ui/slider"
import { Button } from "@/components/ui/button"

export default function Home() {
  return (
    <main className="p-4">
      <Card>
        <CardHeader>
          <img
            src="https://static.asfaltshop.pl/uploads2/photos/big_webp/2021/07/07/20210707033324_morza_poludniowe_cd_real_size_3000px.webp"
            alt="Żyto Morza Południowe"
          />
          <CardTitle>ŻYTO/NOON - „Przypływ”</CardTitle>
          <CardDescription>
            NoweNagrania • 14 tys. subskrybentów
          </CardDescription>
          <Heart />
        </CardHeader>
        <CardContent>
          <Slider defaultValue={[50]} max={100} step={1} />
          <div>
            <span>2:15</span>
            <span>5:00</span>
          </div>
          <div>
            <Button>
              <StepBack />
            </Button>
            <Button>
              <Play />
            </Button>
            <Button>
              <StepForward />
            </Button>
          </div>
        </CardContent>
        <CardFooter>
          <ListMusic />
          <p>Odtwarzane z playlisty "Hip-Hop"</p>
        </CardFooter>
      </Card>
    </main>
  )
}

🎨 Krok 3: Tworzymy style pliku za pomocą tailwind

Teraz ostylujmy cały plik, aby odtwarzacz wyglądał efektowanie.

import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from "@/components/ui/card"
import { Heart, StepBack, Play, StepForward, ListMusic } from "lucide-react"
import { Slider } from "@/components/ui/slider"
import { Button } from "@/components/ui/button"

export default function Home() {
  return (
    // 1. TŁO I CENTROWANIE (Pełny ekran)
    <main className="min-h-[100vh] w-full p-4 flex justify-center items-center bg-slate-50">
      {/* 2. GŁÓWNY KONTENER KARTY */}
      <Card className="min-w-[350px] max-w-[450px] shadow-xl">
        {/* A. NAGŁÓWEK: Okładka + Tytuł + Serce */}
        <CardHeader className="px-4 -my-2 space-y-4">
          <img
            src="https://static.asfaltshop.pl/uploads2/photos/big_webp/2021/07/07/20210707033324_morza_poludniowe_cd_real_size_3000px.webp"
            alt="Żyto Morza Południowe"
            className="rounded-lg aspect-square object-cover shadow-sm"
          />
          <div className="flex justify-between items-start">
            <div className="space-y-1">
              <CardTitle>ŻYTO/NOON - „Przypływ"</CardTitle>
              <CardDescription className="text-xs">
                NoweNagrania • 14 tys. subskrybentów
              </CardDescription>
            </div>
            {/* Interaktywne serduszko */}
            <Button
              className="hover:text-red-500 hover:bg-transparent transition-colors"
              variant="outline"
              size="icon"
            >
              <Heart />
            </Button>
          </div>
        </CardHeader>

        {/* B. TREŚĆ: Suwak + Przyciski sterowania */}
        <CardContent className="px-4 space-y-4 pt-4">
          <div className="space-y-2">
            <Slider defaultValue={[50]} max={100} step={1} />
            <div className="flex justify-between text-sm text-slate-500 font-medium">
              <span>2:15</span>
              <span>5:00</span>
            </div>
          </div>

          <div className="flex items-center justify-center gap-4">
            <Button className="rounded-full" variant="outline" size="icon">
              <StepBack className="w-4 h-4" />
            </Button>
            {/* Duży przycisk Play */}
            <Button
              className="rounded-full w-14 h-14"
              variant="outline"
              size="icon"
            >
              <Play className="w-8 h-8 ml-1" />
            </Button>
            <Button className="rounded-full" variant="outline" size="icon">
              <StepForward className="w-4 h-4" />
            </Button>
          </div>
        </CardContent>

        {/* C. STOPKA: Info o playliście */}
        <CardFooter className="flex justify-center items-center gap-2 pt-2 pb-6">
          <ListMusic className="w-4 h-4 text-slate-500" />
          <p className="text-xs text-slate-500 font-medium">
            Odtwarzane z playlisty "Hip-Hop"
          </p>
        </CardFooter>
      </Card>
    </main>
  )
}

🧠 Analiza: Dlaczego to wygląda dobrze?

Przyjrzyjmy się kluczowym technikom, których użyliśmy w tym projekcie. To one odróżniają "zwykłą stronę" od "nowoczesnego UI".

1. Hierarchia Typografii (Ważne vs Mniej Ważne)

Zauważcie, że nie każdy tekst jest taki sam.

  • Tytuł utworu: Używamy CardTitle, który jest domyślnie pogrubiony i większy.
  • Autor/Subskrypcje: Użyliśmy klasy text-xs (bardzo mały tekst) w CardDescription. Dzięki temu oko użytkownika od razu wie, co jest najważniejsze.
  • Czasy utworów: Są w kolorze szarym (text-slate-500), aby nie odciągały uwagi od suwaka.

2. Układ "Space-Between"

To najczęściej używany trik w UI. Spójrzcie na sekcję z tytułem i serduszkiem:

<div className="flex justify-between">
  <div>Tytuł...</div>
  <Button>Serce</Button>
</div>

Dzięki flex justify-between, tytuł "przykleja się" do lewej krawędzi, a serce do prawej, niezależnie od szerokości karty.

3. Detale Interakcji (Micro-interactions)

Spójrzcie na przycisk "Ulubione" (Serce). Dodałem tam klasę: hover:text-red-500 hover:bg-transparent. To sprawia, że po najechaniu myszką ikona zmienia kolor na czerwony, co daje użytkownikowi natychmiastową informację zwrotną. To te małe smaczki budują jakość.

4. Triki z Marginesami

W CardHeader użyliśmy klasy -my-2 (ujemny margines pionowy). Shadcn domyślnie daje spore odstępy. Czasami, tak jak tutaj, chcemy, aby okładka i tytuł były bliżej siebie. Ujemny margines pozwala nam "złamać" domyślne zasady i zagęścić układ bez psucia reszty karty.


⚔️ Wyzwanie dla Was

Macie działający kod. Teraz przeróbcie go tak, żeby wyrażał utwór, który prezentuje wasz player:

  1. Zmieńcie utwór: Podmieńcie src obrazka na okładkę Waszego ulubionego albumu.
  2. Tryb "Aktywny": Spróbujcie zmienić główny przycisk Play (variant="outline") na wypełniony kolorem (variant="default"), aby bardziej rzucał się w oczy.
  3. Eksperyment z kolorem: Czy potraficie sprawić, by pasek postępu (Slider) był zielony (jak w Spotify)? Podpowiedź: Będziecie musieli zajrzeć do pliku konfiguracyjnego Tailwinda lub nadpisać klasę wewnątrz komponentu.

Pamiętajcie, frontend to sztuka układania gotowych klocków w estetyczną całość. Właśnie zrobiliście duży krok w tę stronę! 🚀

Powodzenia w kodowaniu!