feat: enhance MovieList and MovieRow components with display type toggle and additional props for improved flexibility

This commit is contained in:
Norbert Maciaszek 2025-08-21 21:55:59 +02:00
parent 6bd1b289d7
commit 653380f0fb
5 changed files with 67 additions and 22 deletions

View File

@ -47,6 +47,8 @@ export default async function Home() {
loadMore loadMore
icon={<FaPlay />} icon={<FaPlay />}
colors="blue" colors="blue"
showFilters={false}
displayType="list"
/> />
</section> </section>
@ -57,6 +59,8 @@ export default async function Home() {
loadMore loadMore
icon={<FaCalendar />} icon={<FaCalendar />}
colors="blue" colors="blue"
showFilters={false}
displayType="list"
/> />
</section> </section>
@ -67,6 +71,8 @@ export default async function Home() {
loadMore loadMore
icon={<FaFire />} icon={<FaFire />}
colors="red" colors="red"
showFilters={false}
displayType="list"
/> />
</section> </section>
@ -77,6 +83,8 @@ export default async function Home() {
loadMore loadMore
icon={<FaChartLine />} icon={<FaChartLine />}
colors="green" colors="green"
showFilters={false}
displayType="list"
/> />
</section> </section>

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { useOutsideClick } from "@/hooks/useOutsideClick"; import { useOutsideClick } from "@/hooks/useOutsideClick";
import { FC, useEffect, useRef, useState } from "react"; import { FC, ReactNode, useEffect, useRef, useState } from "react";
import { FaFilter } from "react-icons/fa"; import { FaFilter } from "react-icons/fa";
import { Button } from "../Button"; import { Button } from "../Button";
@ -9,11 +9,17 @@ type Props = {
label: string; label: string;
value: string; value: string;
}[]; }[];
icon?: ReactNode;
defaultValue: string; defaultValue: string;
callback?: (value: string) => void; callback?: (value: string) => void;
}; };
export const Dropdown: FC<Props> = ({ items, defaultValue, callback }) => { export const Dropdown: FC<Props> = ({
items,
icon,
defaultValue,
callback,
}) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState<string>(defaultValue); const [value, setValue] = useState<string>(defaultValue);
@ -27,7 +33,7 @@ export const Dropdown: FC<Props> = ({ items, defaultValue, callback }) => {
return ( return (
<div ref={ref} className="relative inline-block"> <div ref={ref} className="relative inline-block">
<Button theme="glass" size="icon" onClick={() => setIsOpen(!isOpen)}> <Button theme="glass" size="icon" onClick={() => setIsOpen(!isOpen)}>
<FaFilter /> {icon || <FaFilter />}
</Button> </Button>
<div <div

View File

@ -4,9 +4,16 @@ import Link from "next/link";
import { FC } from "react"; import { FC } from "react";
import { FaCalendar, FaClock, FaStar } from "react-icons/fa"; import { FaCalendar, FaClock, FaStar } from "react-icons/fa";
export const MovieRow: FC<{ movie: Movie; isUpcoming: boolean }> = ({ type Props = {
movie: Movie;
isUpcoming?: boolean;
compact?: boolean;
};
export const MovieRow: FC<Props> = ({
movie, movie,
isUpcoming, isUpcoming = false,
compact = false,
}) => { }) => {
const daysSinceRelease = Math.abs( const daysSinceRelease = Math.abs(
Math.floor( Math.floor(
@ -56,17 +63,19 @@ export const MovieRow: FC<{ movie: Movie; isUpcoming: boolean }> = ({
</div> </div>
</div> </div>
<div {!compact && (
className={`text-xs px-2 py-1 rounded-full font-medium ${ <div
isUpcoming className={`text-xs px-2 py-1 rounded-full font-medium ${
? "bg-blue-500/20 text-blue-400" isUpcoming
: "bg-green-500/20 text-green-400" ? "bg-blue-500/20 text-blue-400"
}`} : "bg-green-500/20 text-green-400"
> }`}
{isUpcoming >
? `za ${daysSinceRelease} dni` {isUpcoming
: `od ${daysSinceRelease} dni`} ? `za ${daysSinceRelease} dni`
</div> : `od ${daysSinceRelease} dni`}
</div>
)}
</Link> </Link>
); );
}; };

View File

@ -71,7 +71,7 @@ export const MovieCast: FC<Props> = ({ movieDetails }) => {
{limit < movieDetails.credits.cast.length && ( {limit < movieDetails.credits.cast.length && (
<div className="flex justify-center"> <div className="flex justify-center">
<Button <Button
theme="teal" theme="glass"
size="small" size="small"
className="mt-6" className="mt-6"
onClick={() => { onClick={() => {

View File

@ -7,11 +7,14 @@ import { Dropdown } from "@/components/atoms/Dropdown";
import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useAutoAnimate } from "@formkit/auto-animate/react";
import { Button } from "@/components/atoms/Button"; import { Button } from "@/components/atoms/Button";
import { Label } from "@/components/atoms/Label"; import { Label } from "@/components/atoms/Label";
import { FaList } from "react-icons/fa";
import { MovieRow } from "@/components/atoms/MovieRow";
type Props = { type Props = {
heading?: string; heading?: string;
icon?: ReactNode; icon?: ReactNode;
colors?: keyof typeof colorsMap; colors?: keyof typeof colorsMap;
displayType?: "grid" | "list";
overrideMovies?: Movie[]; overrideMovies?: Movie[];
@ -44,10 +47,12 @@ export const MovieList: FC<Props> = ({
sort: sortType = "releaseDate", sort: sortType = "releaseDate",
sortDirection = "asc", sortDirection = "asc",
loadMore = false, loadMore = false,
displayType = "grid",
}) => { }) => {
const { movies: storeMovies } = useGlobalStore(); const { movies: storeMovies } = useGlobalStore();
const movies = overrideMovies || storeMovies; const movies = overrideMovies || storeMovies;
const [display, setDisplay] = useState<"grid" | "list">(displayType);
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
seen: filterSeenInitial, seen: filterSeenInitial,
favorites: filterFavoritesInitial, favorites: filterFavoritesInitial,
@ -58,7 +63,7 @@ export const MovieList: FC<Props> = ({
sortType sortType
); );
const [loaded, setLoaded] = useState(loadMore ? 4 : movies.length); const [loaded, setLoaded] = useState(loadMore ? 8 : movies.length);
const [parent] = useAutoAnimate(); const [parent] = useAutoAnimate();
const filteredMovies = movies.filter((movie) => { const filteredMovies = movies.filter((movie) => {
@ -189,6 +194,15 @@ export const MovieList: FC<Props> = ({
defaultValue={sort} defaultValue={sort}
callback={(value) => setSort(value as "title")} callback={(value) => setSort(value as "title")}
/> />
<Button
theme="glass"
size="icon"
onClick={() =>
setDisplay(display === "grid" ? "list" : "grid")
}
>
<FaList />
</Button>
</div> </div>
)} )}
</div> </div>
@ -201,14 +215,22 @@ export const MovieList: FC<Props> = ({
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-6 gap-3 sm:gap-6 mt-8 justify-center" className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-6 gap-3 sm:gap-6 mt-8 justify-center"
ref={parent} ref={parent}
> >
{sortedMovies.map((movie) => ( {sortedMovies.map((movie) =>
<MovieCard key={movie.id} layout="aurora" {...movie} /> display === "grid" ? (
))} <MovieCard key={movie.id} layout="aurora" {...movie} />
) : (
<MovieRow key={movie.id} movie={movie} compact />
)
)}
</div> </div>
)} )}
{loadMore && filteredMovies.length > loaded && ( {loadMore && filteredMovies.length > loaded && (
<div className="flex justify-center mt-10"> <div className="flex justify-center mt-10">
<Button theme="teal" onClick={() => setLoaded(movies.length)}> <Button
size="small"
theme="glass"
onClick={() => setLoaded(movies.length)}
>
Pokaż więcej Pokaż więcej
</Button> </Button>
</div> </div>