Files
jellybloom/src/pages/home/rows/brands.tsx
T
2026-03-30 12:32:41 +03:00

100 lines
3.6 KiB
TypeScript

import { useState } from 'react'
import { Filter } from '../../../lib/icons'
import { useHasTmdbKey } from '../../../hooks/use-external'
import { CANON_LISTS } from '../../../lib/canon-lists'
import { STUDIOS, NETWORKS } from '../../../lib/studios-and-networks'
import BrandRow from '../../../components/ui/BrandRow'
import CanonListRow from '../../../components/ui/CanonListRow'
import LetterboxdListRow from '../../../components/ui/LetterboxdListRow'
import LetterboxdAddModal from '../../../components/ui/LetterboxdAddModal'
import { useLetterboxdLists } from '../../../stores/letterboxd-lists-store'
/**
* "From the studios" - one row per major film studio.
*/
export function StudioRows() {
const hasTmdb = useHasTmdbKey()
if (!hasTmdb) return null
return (
<section className="mb-4 mt-4">
<BrandSectionHeader eyebrow="Studios" title="From the studios" subtitle="Films grouped by the company that made them" />
{STUDIOS.map(s => (
<BrandRow key={s.id} brandId={s.id} label={s.label} subtitle={s.blurb} kind="movie" />
))}
</section>
)
}
/**
* "On the networks" - one row per major TV network.
*/
export function NetworkRows() {
const hasTmdb = useHasTmdbKey()
if (!hasTmdb) return null
return (
<section className="mb-4 mt-4">
<BrandSectionHeader eyebrow="Networks" title="On the networks" subtitle="Shows grouped by where they air" />
{NETWORKS.map(n => (
<BrandRow key={n.id} brandId={n.id} label={n.label} subtitle={n.blurb} kind="tv" />
))}
</section>
)
}
function BrandSectionHeader({ eyebrow, title, subtitle }: { eyebrow: string; title: string; subtitle: string }) {
return (
<div className="px-7 mb-5 pt-2">
<div className="flex items-center gap-2 mb-1">
<span className="w-1 h-3.5 rounded-full bg-accent" />
<span className="text-[10.5px] font-semibold text-text-3 uppercase tracking-[0.18em]">{eyebrow}</span>
</div>
<h2 className="font-display text-[20px] font-bold tracking-tight text-text-1 leading-tight">{title}</h2>
<p className="text-[12px] text-text-3 mt-0.5">{subtitle}</p>
</div>
)
}
/**
* Discover canon - renders bundled canon lists (AFI, Sight & Sound, IMDb top).
*/
export function DiscoverCanonSection() {
const hasTmdb = useHasTmdbKey()
if (!hasTmdb) return null
return (
<section className="mb-4 mt-4">
<BrandSectionHeader eyebrow="Canon" title="Discover canon" subtitle="Bundled lists from AFI, Sight & Sound, and IMDb" />
{CANON_LISTS.map(list => (
<CanonListRow key={list.id} list={list} />
))}
</section>
)
}
/**
* Letterboxd lists. Each saved list URL renders as its own row lazy-mounted
* on scroll. The trailing affordance opens a modal to paste a new URL.
*/
export function LetterboxdListsSection() {
const hasTmdb = useHasTmdbKey()
const lists = useLetterboxdLists(s => s.lists)
const [addOpen, setAddOpen] = useState(false)
if (!hasTmdb) return null
return (
<>
{lists.map(saved => (
<LetterboxdListRow key={saved.url} saved={saved} />
))}
<div className="px-7 mb-10">
<button
onClick={() => setAddOpen(true)}
className="inline-flex items-center gap-2 h-9 px-3.5 rounded-full bg-elevated/50 ring-1 ring-border hover:ring-accent/40 hover:text-accent text-[12.5px] text-text-2 tracking-tight transition focus-ring"
>
<Filter size={13} />
{lists.length === 0 ? 'Add a Letterboxd list' : 'Add another Letterboxd list'}
</button>
</div>
<LetterboxdAddModal open={addOpen} onClose={() => setAddOpen(false)} />
</>
)
}