Skip to main content

Overview

Film components are the core building blocks for displaying movie and TV show content in Popcorn Vision. These components handle everything from hero sliders to card grids, managing both visual presentation and user interactions.

HomeSlider

The main hero slider component displayed on the homepage, featuring full-screen film showcases with autoplay and navigation.

Props

films
array
required
Array of film objects to display in the slider
genres
array
required
Genre data for displaying film categories
filmData
array
required
Detailed film data including images (posters, backdrops, logos)

Usage Example

import HomeSlider from "@/components/Film/HomeSlider";

export default function HomePage({ films, genres, filmData }) {
  return (
    <HomeSlider 
      films={films} 
      genres={genres} 
      filmData={filmData} 
    />
  );
}

Features

  • Swiper Integration: Uses Swiper.js with fade effects and keyboard navigation
  • Responsive Images: Serves different images for mobile (poster) and desktop (backdrop)
  • Thumbnail Navigation: Desktop view includes thumbnail previews for quick navigation
  • Accessibility: Includes screen reader-only content with film details
<Swiper
  effect="fade"
  keyboard={true}
  modules={[Pagination, Autoplay, EffectFade, Keyboard, FreeMode, Thumbs]}
>
  {films.map((film) => (
    <SwiperSlide key={film.id}>
      <HomeFilm film={film} genres={genres} />
    </SwiperSlide>
  ))}
</Swiper>

FilmSlider

Horizontal scrolling carousel for displaying collections of films with navigation controls.

Props

films
object
required
Object containing results array of films
title
string
required
Section title displayed above the slider
sort
string
default:"DESC"
Sort order for films: "DESC" or "ASC"
viewAll
string
default:""
URL for “View all” link. If empty, link is not shown

Usage Example

<FilmSlider
  films={trendingFilms}
  title="Trending Now"
  sort="DESC"
  viewAll="/search?sort_by=popularity.desc"
/>

Implementation Details

Slides per group adjust based on screen size:
  • 640px: 3 slides
  • 768px: 4 slides
  • 1024px: 5 slides
  • 1280px: 6 slides
  • 1536px: 7 slides

FilmCard

Individual film card component with poster image, rating badge, and hover effects.

Props

film
object
required
Film object with properties:
  • id: Film ID
  • title or name: Film title
  • poster_path: Poster image path
  • vote_average: Rating (0-10)
  • release_date or first_air_date: Release date
isTvPage
boolean
required
Whether the card is for a TV show

Features

Hover Preview

Integrates with useHoverCard store to show detailed preview on hover

Rating Badge

Color-coded rating indicator:
  • Red: 0-3
  • Yellow: 3-7
  • Green: 7-10

Lazy Loading

Images load lazily with loading="lazy" attribute

Accessibility

Screen reader-only heading with title and year

Usage Example

Film/Card.jsx (excerpt)
import FilmCard from "@/components/Film/Card";
import { useHoverCard } from "@/zustand/hoverCard";

export default function FilmCard({ film, isTvPage }) {
  const { handleMouseOver } = useHoverCard();

  return (
    <Link
      href={`/${!isTvPage ? 'movies' : 'tv'}/${film.id}-${slug(film.title ?? film.name)}`}
      onMouseEnter={(e) => {
        const position = e.target.getBoundingClientRect();
        handleMouseOver(film, position);
      }}
    >
      <h3 className="sr-only">
        {film.title ?? film.name} ({dayjs(film.release_date ?? film.first_air_date).format("YYYY")})
      </h3>
      
      <ImagePovi imgPath={film.poster_path}>
        <img
          src={`https://image.tmdb.org/t/p/w300${film.poster_path}`}
          loading="lazy"
          alt=""
        />
        
        {/* Rating Badge */}
        {film.vote_average > 0 && (
          <div className="radial-progress">
            <span>{formatRating(film.vote_average)}</span>
          </div>
        )}
      </ImagePovi>
    </Link>
  );
}

FilmGrid

Responsive grid layout for displaying multiple films with infinite scroll loading.

Props

films
array
required
Array of film objects to display
fetchMoreFilms
function
required
Callback function to fetch additional films when scrolling
currentSearchPage
number
required
Current page number
totalSearchPages
number
required
Total number of pages available
loading
boolean
required
Loading state indicator
initialLoading
boolean
default:false
Whether this is the initial page load

Infinite Scroll Implementation

const loadMoreRef = useRef(null);

useEffect(() => {
  const observer = new IntersectionObserver(
    (entries) => {
      if (entries[0].isIntersecting) {
        fetchMoreFilms();
      }
    },
    { threshold: 0.5 }
  );

  if (loadMoreRef.current) {
    observer.observe(loadMoreRef.current);
  }

  return () => {
    if (loadMoreRef.current) {
      observer.unobserve(loadMoreRef.current);
    }
  };
}, [fetchMoreFilms]);

Grid Breakpoints

The grid uses container queries for responsive layouts:
  • Base: 3 columns
  • @2xl: 4 columns
  • @5xl: 5 columns
  • @6xl: 6 columns
  • @7xl: 7 columns

FilmSummary

Displays film metadata including title, rating, runtime, genres, and overview.

Props

film
object
required
Film object with detailed information
className
string
Additional CSS classes
btnClass
string
CSS classes for buttons
showButton
boolean
default:true
Whether to show the “Details” button
clampDescription
boolean
default:true
Whether to clamp the overview text (2-3 lines)

Features

<Link href={`/search?rating=${formatRating(film.vote_average)}..10`}>
  <IonIcon icon={star} />
  <span>{formatRating(film.vote_average)}</span>
</Link>
Rating badge links to search results with similar or higher ratings.

Recommendation

Displays recommended and similar films with infinite scroll pagination.

Props

id
number
required
Film ID to fetch recommendations for
similar
object
required
Similar films data from API
recommendations
object
required
Recommended films data from API
title
string
required
Section title (e.g., “You May Also Like”)

Implementation

The component first shows recommendations, then switches to similar films when recommendations are exhausted:
const fetchMoreFilms = async () => {
  let endpoint = !isFinished ? 'recommendations' : 'similar';
  
  const response = await axios.get(
    `/api/${!isTvPage ? 'movie' : 'tv'}/${id}/${endpoint}`,
    { params: { page: nextPage } }
  );
  
  setFilmsData((prev) => [...prev, ...response.results]);
};

TitleLogo

Displays film logo or fallback title text

ImagePovi

Image wrapper with fallback and loading states

User Actions

Watchlist, favorite, and rating buttons

Search Integration

Filter and search components

Key Files

src/components/Film/
├── HomeSlider.jsx      # Hero slider component
├── Slider.jsx          # Horizontal film carousel
├── Card.jsx            # Individual film card
├── Grid.jsx            # Responsive grid layout
├── Summary.jsx         # Film metadata display
├── Recommendation.jsx  # Related films section
└── TitleLogo.jsx       # Film logo component

Build docs developers (and LLMs) love