Oblikovanje
U prethodnim primjerima smo napravili funkcionalnu aplikaciju pomoću koje možemo dohvatiti imena Pokemona i uz pomoć dinamičkih ruta pregledati detalje svakog Pokemona pojedinačno. Trenutno je najveći nedostatak aplikacije izostanak bilo kakvog oblikovanja, pa ćemo sada malo doraditi izgled naše aplikacije i usput se upoznati sa osnovama oblikovanja u Next.js-u.
Globalni stil
Krenuti ćemo od oblikovanja uz pomoć globalnih stilova. Prvo je potrebno provjeriti imamo li na stranici app/layout.js uključenu naredbu za import globalnog stila.
import "./globals.css";
export const metadata = {
title: 'Next.js Pokédex',
description: 'Pokédex aplikacija u Next.js uz App Router',
};
export default function RootLayout({ children }) {
return (
<html lang="hr">
<body>
<header>JuniorDev Next.js</header>
{children}
</body>
</html>
);
}Nakon što smo to provjerili, možemo definirati neke osnovne stilove:
body {
margin: 0;
font-family: sans-serif;
background-color: #f0f0f0;
}
main {
padding: 20px;
}
h1 {
color: #ff0000;
}
ul {
list-style: none;
padding-left: 0;
}
.plaviTekst{
color: #2048ca;
}Globalni stilovi se uglavnom koriste kako bi napravili reset nekih pravila (npr. koji su postavljeni od strane preglednika) ili za postavljanje osnovnih postavki na razini cijele aplikacije (npr. box-sizing: border-box;). Također ih koristimo i za tipografiju (fontove) koju u pravilu želimo imati konzistentnu na razini cijele stranice.
U ovom primjeru je na kraju datoteke definiran selektor klase "plaviTekst" sa pravilom koje postavlja boju teksta na nijansu plave boje. S obzirom da se ova CSS datoteka primjenjuje na glavni layout, ovu klasu možemo koristiti na bilo kojoj stranici (možemo to jednostavno testirati na stranici sa detaljima Pokemona).
Lokalni (modulski) CSS
Ukoliko želimo definirati oblikovanje na razini jedne stranice/komponente, možemo koristiti modulski pristup. Sve što moramo napraviti je, u istoj lokaciji gdje nam se nalazi stranica koju želimo oblikovati, stvoriti novu datoteku pod nazivom page.module.css. Za primjer možemo definirati stilove za našu dinamičku stranicu app/pokemon/name/page.js, što znači da ćemo napraviti datoteku app/pokemon/[name]/page.module.css:
.title {
color: #3498db;
text-transform: capitalize;
}
.info {
background: white;
padding: 10px;
border-radius: 8px;
}Naravno, moramo na stranici za prikaz dodati odgovarajuće klase kako bi se gore napisana pravila primijenila na elemente stranice.
import styles from './page.module.css';
export default async function PokemonDetalji({ params }) {
const { name } = params;
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
const data = await res.json();
return (
<main className={styles.info}>
<h1 className={styles.title}>{data.name}</h1>
<p className="plaviTekst">ID: {data.id}</p>
<p>Težina: {data.weight}</p>
<p>Visina: {data.height}</p>
</main>
);
}Uočite kako smo odmah na početku napravili učitavanje CSS modula pomoću import naredbe i spremili napisana pravila u styles objekt. Osnovna ideja modula je što svaka datoteka .module.css generira lokalno jedinstvena imena klasa koja će biti jedinstvena za tu datoteku i neće “procuriti” u globalni raspon. Time smo dobili elegantno rješenje za izbjegavanje konflikata u imenima klasa, ne moramo misliti jesmo li neko ime klase već upotrijebili u drugom dijelu aplikacije i hoće li doći do kolizije i preklapanja pravila oblikovanja.
U ovom primjeru smo upotrijebili i globalni stil "plaviTekst" kako bi pokazali da globalna pravila iz prvog primjera vrijede na svim stranicama. S druge strane, ako na početnoj layout.js stranici dodamo npr. klasu "info" koja je definirana u modul datoteci, ništa se neće dogoditi tj. pravilo ostaje samo na lokalnoj razini.
import "./globals.css";
export const metadata = {
title: 'Next.js Pokédex',
description: 'Pokédex aplikacija u Next.js uz App Router',
};
export default function RootLayout({ children }) {
return (
<html lang="hr">
<body>
<header className="info">JuniorDev Next.js</header>
{children}
</body>
</html>
);
}Možemo iskoristiti i mogućnosti našeg web preglednika i pogledati konačni HTML oblik naše stranice. Otvorimo detaljni prikaz jednog Pokemona i pogledajmo imena klasa <header> elementa (koji je dio svake stranice jer je definiran u layout.js datoteci) te <main> elementa kojeg smo oblikovali pomoću modula. Preglednik će vam pokazati ovako nešto:
<header class="info">JuniorDev Next.js</header>
<main class="page_info____smg"></main>Iako bi oba elementa trebala imati klasu "info", možemo vidjeti kako se zbog korištenja CSS modula klasa info iz stranice sa detaljima preimenovala u drugi naziv. Ovaj naziv će biti jedinstven na razini cijele stranice, tako da možemo imati više CSS modula pridruženih različitim stranicama bez da dođe do konflikta sa imenima klasa. Iako u .module.css datoteci pišemo .title ili .info, Next.js (tj. da budemo precizniji Webpack/Turbopack) će u konačnoj HTML datoteci generirati hashirane nazive kao u gornjem primjeru.
Napomena: Kod CSS modula možemo koristiti samo tzv. “pure” (čiste) selektore, tj. moramo izbjegavati globalne selektore (selektore elemenata) poput body, h1, ul unutar .module.css jer bi se time izgubio smisao lokalnog dosega. Čisti selektori su selektori koji imaju barem jedan atribut klase ili id-a koji se nalazi u lokalnom dosegu datoteke.
Dodatno ćemo to demonstrirati i pisanjem globalnog selektora za info klasu.
.info{
background-color: #333;
color: #fff;
padding: 1rem;
margin-bottom: 1rem;
text-align: center;
font-size: 1.5rem;
}Nakon pokretanja (ili osvježavanja) aplikacije, ovo pravilo će se odnositi samo na <header> element, dok će za <main> i dalje vrijediti jedinstveno generirano ime klase koje je povezano sa pridruženim CSS modulom.
Sada imamo (barem donekle) oblikovanu aplikaciju, pa možemo nastaviti sa pregledom osnovnih funkcionalnosti Next.js-a.
