Lightbox

Floor plan

View as Markdown

Rooms open from the plan. In the viewer, a mini plan is the thumbnail strip, and its highlight glides from room to room.

import { cn } from 'cn'
import { mixed } from '../../data/photos'
import { Arrows, Chrome, Lightbox, Slides, Toolbar, useLightbox } from '../lightbox'

// A 6 × 4 grid; the order is the swipe order, so neighbours in the viewer are neighbours on the plan.
const rooms = (
  [
    ['Living room', 'living-room', 'col-span-3 row-span-2'],
    ['Kitchen', 'kitchen', 'col-span-3'],
    ['Dining', 'dining-room', 'col-span-3'],
    ['Terrace', 'pool', 'col-start-5 row-start-3 col-span-2 row-span-2'],
    ['Study', 'armchair', 'col-start-4 row-start-3 row-span-2'],
    ['Bath', 'bathroom', 'col-start-3 row-start-3 row-span-2'],
    ['Bedroom', 'bedroom', 'col-start-1 row-start-3 col-span-2 row-span-2'],
  ] as const
).map(([caption, name, area]) => ({ ...mixed.find((p) => p.src === `/photos/${name}.webp`)!, caption, area }))

const plan = 'grid grid-cols-6 grid-rows-4'

/** The plan in the viewer: a thumbnail strip laid out as the house. The indicator glides from room to room. */
function MiniPlan() {
  const { item } = useLightbox<(typeof rooms)[number]>()
  return (
    <div className="pointer-events-auto absolute inset-x-0 bottom-0 flex flex-col items-center gap-2 pb-[max(1rem,env(safe-area-inset-bottom))]">
      <p className="text-sm font-medium">{item?.caption}</p>
      <Lightbox.Thumbnails className={cn(plan, 'h-24 w-36 gap-0.5 rounded-md bg-white/25 p-0.5')}>
        {rooms.map((room, i) => (
          <Lightbox.Thumbnail
            key={room.src}
            index={i}
            aria-label={room.caption}
            className={cn('rounded-[2px] bg-neutral-800 outline-none hover:bg-neutral-700', room.area)}
          />
        ))}
        <Lightbox.ThumbnailIndicator className="rounded-[2px] bg-amber-300" />
      </Lightbox.Thumbnails>
    </div>
  )
}

export default function FloorPlan() {
  return (
    <Lightbox.Root items={rooms}>
      {/* The gaps are the walls. */}
      <div className={cn(plan, 'h-80 gap-1 overflow-hidden rounded-xl border-4 border-neutral-800 bg-neutral-800')}>
        {rooms.map((room, i) => (
          <Lightbox.Trigger
            key={room.src}
            index={i}
            className={cn(
              'group relative overflow-hidden bg-amber-300 outline-none focus-visible:ring-2 focus-visible:ring-amber-300 focus-visible:ring-inset',
              room.area,
            )}
          >
            <img src={room.thumb} alt={room.caption} className="size-full object-cover" />
            <span className="absolute top-1.5 left-1.5 rounded bg-white/90 px-1.5 py-0.5 text-xs font-medium text-neutral-900 group-data-active:bg-transparent">
              {room.caption}
            </span>
          </Lightbox.Trigger>
        ))}
      </div>
      <Lightbox.Portal>
        <Lightbox.Backdrop className="z-50 bg-neutral-950" />
        <Lightbox.Popup className="group z-50 text-white outline-none">
          <Slides className="pb-40 sm:pb-40" />
          <Chrome>
            <Toolbar />
            <Arrows />
            <MiniPlan />
          </Chrome>
        </Lightbox.Popup>
      </Lightbox.Portal>
    </Lightbox.Root>
  )
}