Lightbox

Повноекранний компонент Lightbox-галереї зображень і відео на основі @reelkit/react-lightbox.

Подивитися демо наживо →

Features

Зображення та відео
Вбудована підтримка слайдів-відео
Дотикові жести
Свайп для гортання
Свайп для закриття
Свайп угору закриває
Навігація з клавіатури
Стрілки + Escape
Повний екран
Кросбраузерний API
Переходи
Зсув, затухання, переворот, наближення
Preloading
Сусідні зображення завантажуються заздалегідь
Перемикач звуку
Звук вмикається й вимикається для кожного слайда
Стани завантаження
Індикатор і власний рендеринг
Обробка помилок
Значок помилки та власний рендеринг
Render Props
6 налаштовних зон рендерингу
Hooks
useVideoSlideRenderer + useFullscreen
Стан в URL
Посилання, якими можна ділитися й зберігати в закладки
Слайдшоу
Автоперехід за бажанням, із відтворенням і паузою
Завантаження
Кнопка за бажанням, що зберігає активний елемент

Встановлення

bash
npm install @reelkit/react-lightbox @reelkit/react lucide-react

Не забудьте імпортувати стилі:

typescript
import '@reelkit/react-lightbox/styles.css';
Icons

Стандартні елементи керування використовують lucide-react для іконок. Якщо ви віддаєте перевагу іншій бібліотеці іконок, скористайтеся renderControls та renderNavigation , щоб передати власні.

Швидкий старт

The LightboxOverlay показує зображення на весь екран. Передайте масив об’єктів LightboxItem і керуйте видимістю через індекс, який може бути null.

tsx
import { useState } from 'react';
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: 'https://example.com/image1.jpg',
    title: 'Sunset',
    description: 'Beautiful sunset over the ocean',
  },
  {
    src: 'https://example.com/image2.jpg',
    title: 'Mountains',
  },
];

function App() {
  const [index, setIndex] = useState<number | null>(null);

  return (
    <>
      {images.map((img, i) => (
        <img
          key={i}
          src={img.src}
          onClick={() => setIndex(i)}
        />
      ))}
      <LightboxOverlay
        isOpen={index !== null}
        images={images}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
      />
    </>
  );
}
LightboxPage.tsx
import { useState } from 'react';
import {
  LightboxOverlay,
  flipTransition,
  lightboxFadeTransition,
  lightboxZoomTransition,
  slideTransition,
  type LightboxItem,
} from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';
import '@reelkit/react-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: '/cdn/samples/images/image-01.jpg',
    title: 'Mountain River',
    description: 'A beautiful mountain river flowing through the forest',
    width: 1600,
    height: 1000,
  },
  {
    src: '/cdn/samples/images/image-02.jpg',
    title: 'Snowy Peaks',
    description: 'Majestic snow-capped mountains reaching for the sky',
    width: 1000,
    height: 1600,
  },
  {
    src: '/cdn/samples/images/image-03.jpg',
    title: 'Foggy Forest',
    description: 'Misty morning in the dense forest',
    width: 1600,
    height: 900,
  },
  {
    src: '/cdn/samples/images/image-04.jpg',
    title: 'Ocean Waves',
    description: 'Powerful ocean waves crashing against the rocky shore',
    width: 900,
    height: 1400,
  },
  {
    src: '/cdn/samples/images/image-05.jpg',
    title: 'Autumn Path',
    description: 'A winding path through the autumn forest',
    width: 1600,
    height: 1067,
  },
  {
    src: '/cdn/samples/images/image-06.jpg',
    title: 'Coastal Cliffs',
    description: 'Dramatic coastal cliffs overlooking the deep blue sea',
    width: 1600,
    height: 1067,
  },
];

const transitions: { label: string; fn: TransitionTransformFn }[] = [
  { label: 'slide', fn: slideTransition },
  { label: 'fade', fn: lightboxFadeTransition },
  { label: 'flip', fn: flipTransition },
  { label: 'zoom-in', fn: lightboxZoomTransition },
];

export default function App() {
  const [index, setIndex] = useState<number | null>(null);
  const [transitionFn, setTransitionFn] = useState<TransitionTransformFn>(
    () => slideTransition,
  );

  return (
    <div style={{ padding: 16, background: '#f8fafc', minHeight: '100vh' }}>
      {/* Transition picker */}
      <div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>
        {transitions.map((t) => (
          <button
            key={t.label}
            onClick={() => setTransitionFn(() => t.fn)}
            style={{
              padding: '6px 14px', borderRadius: 8, border: 'none', cursor: 'pointer',
              fontSize: 13, fontWeight: 500,
              background: transitionFn === t.fn ? '#6366f1' : '#e2e8f0',
              color: transitionFn === t.fn ? '#fff' : '#334155',
            }}
          >
            {t.label}
          </button>
        ))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
        {images.map((img, i) => (
          <button
            key={i}
            onClick={() => setIndex(i)}
            style={{
              position: 'relative', aspectRatio: '4 / 3', borderRadius: 8,
              overflow: 'hidden', border: 'none', padding: 0, cursor: 'pointer',
              background: '#e2e8f0',
            }}
          >
            <img
              src={img.src}
              alt={img.title}
              style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
            />
          </button>
        ))}
      </div>
      <LightboxOverlay
        isOpen={index !== null}
        images={images}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
        transitionFn={transitionFn}
      />
    </div>
  );
}

Щоб показати галерею як слайдшоу й зберігати оригінал кожного фото, дивіться рецепт «Галерея зі слайдшоу».

Слайди-відео (за бажанням)

Підтримка відео вмикається за бажанням і піддається tree-shaking — використання лише зображень нічого не додає до бандла. Імпортуйте useVideoSlideRenderer і передайте повернені значення у LightboxOverlay. Хук сам дає раду станам завантаження, керуванню звуком і життєвим циклом відео.

tsx
import {
  LightboxOverlay,
  useVideoSlideRenderer,
  type LightboxItem,
} from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';

const items: LightboxItem[] = [
  { src: '/photo.jpg', title: 'Photo' },
  {
    src: '/clip.mp4',
    type: 'video',
    poster: '/clip-thumb.jpg',
    title: 'Video Clip',
  },
];

function Gallery() {
  const [index, setIndex] = useState<number | null>(null);
  const isOpen = index !== null;
  const { renderSlide, renderControls, SoundProvider } =
    useVideoSlideRenderer(items);

  return (
    <SoundProvider>
      {/* thumbnails… */}
      <LightboxOverlay
        isOpen={isOpen}
        images={items}
        initialIndex={index ?? 0}
        onClose={() => setIndex(null)}
        renderSlide={renderSlide}
        renderControls={renderControls}
      />
    </SoundProvider>
  );
}
Як це працює
  • Хук повертає SoundProvider — загорніть у нього оверлей, щоб перемикання звуку працювало
  • Відео відтворюється автоматично (типово без звуку), коли слайд стає активним
  • Спільний елемент відео повторно використовується між слайдами заради безперервності звуку на iOS
  • Кнопка звуку з’являється на слайдах-відео автоматично, з реактивним перемикачем
  • Елементи без type: 'video' рендеряться як зображення (зворотна сумісність)

Customization

Власні елементи керування

Використовуйте renderControls щоб замінити стандартну кнопку закриття, лічильник і перемикач повного екрана. Складайте з експортованих підкомпонентів:

tsx
import {
  LightboxOverlay,
  CloseButton,
  Counter,
  FullscreenButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderControls={({ onClose, activeIndex, count, isFullscreen, onToggleFullscreen }) => (
    <div style={{ position: 'absolute', top: 12, left: 16, right: 16, display: 'flex', justifyContent: 'space-between', zIndex: 10 }}>
      <Counter currentIndex={activeIndex} count={count} />
      <div>
        <FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />
        <CloseButton onClick={onClose} />
      </div>
    </div>
  )}
/>

Власний інформаційний оверлей

Використовуйте renderInfo щоб замінити стандартний градієнт із заголовком та описом, або передайте renderInfo={() => null} щоб сховати його повністю:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderInfo={({ item, index }) => (
    <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, background: 'linear-gradient(transparent, rgba(0,0,0,0.8))', color: '#fff', zIndex: 10 }}>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </div>
  )}
/>

Власна навігація

Використовуйте renderNavigation щоб замінити стандартні стрілки вперед і назад:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderNavigation={({ onPrev, onNext, activeIndex, count }) => (
    <div style={{ position: 'absolute', bottom: 24, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 12, zIndex: 10 }}>
      <button onClick={onPrev} disabled={activeIndex === 0}>Prev</button>
      <span>{activeIndex + 1} / {count}</span>
      <button onClick={onNext} disabled={activeIndex === count - 1}>Next</button>
    </div>
  )}
/>

Власний слайд

Використовуйте renderSlide для повністю власного вмісту слайда. Поверніть null щоб лишити стандартний слайд-зображення:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={handleClose}
  renderSlide={({ item, index, size, isActive, onReady, onError }) => {
    // Custom CTA on last slide
    if (index === images.length - 1) {
      return (
        <div style={{ width: size[0], height: size[1], display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
          <h2>View all photos</h2>
        </div>
      );
    }
    return null; // default image slide
  }}
/>

Завантаження вмісту та обробка помилок

Lightbox стежить за станом завантаження й помилок кожного слайда. Поки вміст вантажиться, показується індикатор; для медіа, що не завантажилося, — значок зіпсованого зображення. URL з помилками кешуються, тож повторний перехід одразу показує помилку без нової спроби.

Колбеки життєвого циклу

Якщо ви використовуєте renderSlide, call these callbacks to control the loading indicator:

КолбекКоли викликати
onReadyЗображення завантажилося або відео почало грати. Скидає стани завантаження й помилки.
onWaitingВідео буферизується посеред відтворення. Показує індикатор завантаження.
onErrorВміст не завантажився. Показує оверлей помилки й кешує URL як зіпсований.
tsx
// Inside renderSlide — wire callbacks to your custom media
renderSlide={({ item, index, size, isActive, onReady, onWaiting, onError }) => (
  <div style={{ width: size[0], height: size[1] }}>
    {item.type === 'video' ? (
      <video
        src={item.src}
        poster={item.poster}
        autoPlay={isActive}
        onCanPlay={onReady}
        onWaiting={onWaiting}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
      />
    ) : (
      <img
        src={item.src}
        onLoad={onReady}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
      />
    )}
  </div>
)}

Власний інтерфейс завантаження та помилок

Замініть стандартний індикатор і значок помилки власними компонентами:

tsx
<LightboxOverlay
  isOpen={isOpen}
  images={images}
  onClose={() => setIsOpen(false)}
  renderLoading={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: '#fff', fontSize: 14,
    }}>
      Loading image {activeIndex + 1}...
    </div>
  )}
  renderError={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      gap: 12, color: 'rgba(255,255,255,0.5)',
    }}>
      <span style={{ fontSize: 48 }}>!</span>
      <span>Failed to load content</span>
    </div>
  )}
/>

Стан в URL

Подивитися демо наживо →

LightboxUrlOverlay — окремий компонент, стан відкриття якого живе в адресному рядку. Побудуйте контролер через useOverlayUrlState from @reelkit/react і передайте його як controller: галерея відкривається сама, коли параметр називає слайд, і закривається, коли параметр зникає. Посиланнями можна ділитися, а кнопка «назад» закриває галерею, а не виводить зі сторінки.

Вбудовані клавіші

Слайди можна адресувати вбудованим ключем — розгорніть urlIndexKey (за позицією) або urlStableIdKey (за стабільним id) into the controller — both re-exported from @reelkit/react. See the посібник зі стану в URL та API ядра.

tsx
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/react';
import { LightboxUrlOverlay } from '@reelkit/react-lightbox';
import { Link } from 'react-router-dom';

const photo = useOverlayUrlState({
  param: 'photo',
  ...urlIndexKey(() => images.length),
});

// Opening is a link — the href is the open action. No open flag, no handler:
// the overlay reads the URL and opens itself.
{images.map((image, i) => (
  <Link key={image.src} to={`?photo=${i}`}>
    <img src={image.src} />
  </Link>
))}

<LightboxUrlOverlay controller={photo} images={images} />

Хук приймає один об’єкт опцій і повертає UrlStateController (with set, index, value). Keep it for programmatic control: set — низькорівневий запис, який оверлей робить усередині (зміна слайда та set(null) для закриття). Ним же можна керувати оверлеєм програмно: set(index) відкриває його — так само, як перехід за параметром. Але для відкриття краще звичайне посилання: href можна надіслати, відкрити в новій вкладці, а кнопка «назад» його закриє — і все це без жодного обробника.

Full useOverlayUrlState options (param, adapter, codec, locator): see the довіднику API для React.

LightboxUrlOverlay приймає лише controller (обов’язково), необов’язковий onClose, plus every visual and behavior prop LightboxOverlay takes (images, ariaLabel, transitionFn, the render props, and so on) — but no isOpen.

  • Відкриття коштує одного запису в історії. Гортання слайдів замінює його, тож сто свайпів не додають жодного — один крок назад завжди виходить із галереї. «Назад» закриває, а не гортає фотографії.
  • Надіслане посилання на кшталт ?photo=3 відкриває галерею на цьому слайді. Закриття посилання, що прийшло разом зі сторінкою, прибирає параметр на місці, а не виводить із сайту.
  • «Назад» закриває лише тоді, коли ви відкрили галерею всередині застосунку — посилання додало запис, тож «назад» повертає до галереї. За надісланим посиланням у новій вкладці історії позаду немає, тож кнопка «назад» виведе із сайту; кнопка закриття або Escape прибирає параметр на місці й лишає вас у галереї. Готові адаптери роутерів ручаються за перехід за посиланням на тій самій сторінці; власний адаптер, який не може сказати, як з’явився запис, теж отримує закриття на місці, і в історії лишається дублікат сторінки — один крок назад тоді ніби нічого не робить, але нічого й не відкриває.
  • Параметр, який не називає жодного слайда — застаріла закладка, змінене вручну значення, — прибирається з URL, а не лишає в адресному рядку слайд, який не відкриється.

У застосунку з роутером передайте адаптер. Прямий запис в історію лишає власне місцеположення роутера застарілим, і наступна навігація втрачає параметр.

tsx
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';

const adapter = useReactRouterUrlAdapter();
const photo = useOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => images.length),
});

<LightboxUrlOverlay controller={photo} images={images} />

Відкриття — це посилання. Оскільки стан відкриття живе в URL, мініатюра — звичайне посилання без обробника кліку, і вся поведінка браузера дістається безкоштовно: відкрити в новій вкладці, скопіювати адресу, побачити підказку при наведенні. У застосунку з роутером беріть посилання роутера, щоб перехід лишався на клієнті.

tsx
import { Link } from 'react-router-dom';

// The href is the open action — no onClick, no open flag.
{images.map((image, i) => (
  <Link key={image.src} to={`?photo=${i}`}>
    <img src={image.src} />
  </Link>
))}

<LightboxUrlOverlay controller={photo} images={images} />

Для посилань, якими діляться, краще стабільна ідентичність. Індекс адресує за позицією, тож збережений у закладках ?photo=3 відкриє інше зображення, щойно список перевпорядкують. urlStableIdKey адресує за стабільним id, scanning the live list — one call covers the common case.

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  ...urlStableIdKey({ items: () => images }),
});

<LightboxUrlOverlay controller={photo} images={images} />

Pass hashCodec: base64UrlCodec щоб закодувати id в URL у base64url — оборотне маскування, а не криптографічний хеш.

Адресуєте за іншим полем ( slug), or page an infinite feed with locateAsync, and build the codec/locator самі:

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.slug === id),
    identify: (index) => images[index].slug,
  },
});

<LightboxUrlOverlay controller={photo} images={images} />

Нескінченні та посторінкові галереї. Синхронний locate відповідає лише за вже завантажені зображення — надіслане посилання на зображення 400 у стрічці, де завантажено 20, нічого не знайде. locateAsync — запасний варіант, що викликається лише коли locate misses.

Shortcut

Keying by the item’s id? Не пишіть кодек і локатор вручну — передайте locateAsync просто в urlStableIdKey({ items, locateAsync }) (він вантажить дані, якщо не знайшов, і повертає індекс). Розгорнутий варіант нижче — для адресації за іншим полем або для повного контролю.

tsx
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.id === id),
    identify: (index) => images[index].id,
    locateAsync: async (id) => {
      const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
      if (!loaded) return null; // exhausted — link names no image
      setImages(loaded); // commit — the overlay renders from this state
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});

<LightboxUrlOverlay controller={photo} images={images} />
  • Як саме вантажити — ваша справа: тягніть сторінки поспіль до потрібної або лише одне зображення й додайте його. URL адресує за ідентичністю, а не за позицією, тож findIndex поверне те місце, де елемент опинився.
  • While locateAsync у процесі, Lightbox лишається закритим, а параметр — недоторканим, тож пряме посилання переживає запит. null або відмова прибирає параметр.
  • Відповідь, що приходить після зміни URL, після закриття або після демонтажу, відкидається — повільний запит не відкриє слайд, якого ніхто не просив.
  • Поки триває очікування, нічого не рендериться: цей стан завантаження вже належить сторінці, тож малюйте власний скелетон.
  • Тайм-ауту немає — Lightbox не може знати, яка галерея завдовжки. Завершуйте значенням null коли сторінки скінчилися, інакше оверлей лишиться закритим назавжди.
  • Whatever locateAsync є остаточним — це індекс щойно завантажених даних, узятий як є, без повторного читання images.

Автоперехід

Задайте autoAdvance, щоб галерея після відкриття гралася як слайдшоу. Типово вимкнено, тож наявний Lightbox рендериться й поводиться як раніше.

tsx
import { LightboxOverlay } from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  autoAdvance
  autoAdvanceInterval={4000}
/>;
  • Зображення лишаються на екрані autoAdvanceInterval (типово 5000 мс), відлічуваних із моменту, коли зображення завантажилося, тож повільне зображення ніколи не пропускається, перш ніж його хтось побачив. Для зображення, що не завантажилося, відлік іде від помилки.
  • Відео програються один раз замість циклу й переходять далі, коли закінчуються. З useVideoSlideRenderer це під’єднано за вас; власний renderSlide читає autoAdvance зі своїх пропсів і повідомляє про кінець через onEnded.
  • Воно чекає, поки миша лежить на елементі керування (кнопці, посиланні чи підписі), під час свайпу і поки сторінка прихована, а потім продовжує з місця зупинки.
  • Ручне перемикання (стрілки, клавіші, колесо, свайп) запускає інтервал нового слайда з нуля; слайдшоу лишається увімкненим.
  • На останньому слайді воно зупиняється, якщо не задано loop. Кнопка відтворення й паузи серед елементів керування показує стан і дає глядачеві його перемкнути.

Власний renderControls отримує isAutoAdvancing і, лише коли autoAdvance увімкнено, onToggleAutoAdvance — рендерте PlayPauseButton, коли він є. Сам таймінг — це createSlideshowController з @reelkit/core; див. посібник з автовідтворення, щоб вбудувати ту саму поведінку у власний плеєр, і API ядра щодо його методів.

Завантаження

Задайте showDownload, щоб додати до елементів керування кнопку завантаження. Вона зберігає активний елемент за його downloadUrl, а якщо його немає — за src, під іменем downloadName, а якщо його немає — під останнім сегментом URL.

tsx
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';

const images: LightboxItem[] = [
  {
    src: 'https://cdn.example.com/beach-1600.jpg',
    // Save the original rather than the resized preview
    downloadUrl: 'https://cdn.example.com/beach-original.jpg',
    downloadName: 'beach.jpg',
  },
];

<LightboxOverlay
  isOpen={index !== null}
  images={images}
  initialIndex={index ?? 0}
  onClose={() => setIndex(null)}
  showDownload
/>;

Файл завантажується через fetch і зберігається, бо браузер ігнорує атрибут download на посиланні на інше джерело й відкрив би файл замість збереження. Коли fetch не вдається — зазвичай це сервер на іншому джерелі, що не надсилає заголовків CORS, — файл відкривається в новій вкладці, де глядач усе ще може його зберегти. Віддавайте файли з Access-Control-Allow-Origin, щоб зберігати одним кліком. Поки триває завантаження, кнопка вимкнена.

Власний renderControls отримує isDownloading і, лише коли showDownload увімкнено, onDownload — рендерте DownloadButton, коли він є. Помічник — це downloadFile з @reelkit/core; див. посібник із завантаження.

Довідник API

Пропси LightboxOverlay

LightboxOverlayProps

PropТипТипове значенняОпис
isOpenbooleanrequiredКерує видимістю Lightbox. Для стану відкриття, керованого URL, беріть окремий LightboxUrlOverlay — дивіться розділ про стан в URL нижче.
imagesLightboxItem[]requiredМасив зображень для показу
ariaLabelstring'Image gallery'Доступна назва області діалогу; екранні читачі оголошують її, коли Lightbox відкривається
initialIndexnumber0Початковий індекс зображення
transitionFnTransitionTransformFnslideTransitionФункція переходу між слайдами. Імпортуйте вбудовану (slideTransition, flipTransition, lightboxFadeTransition, lightboxZoomTransition) або передайте власну. Якщо не задано, використовується slideTransition.
autoAdvancebooleanfalseГрати галерею як слайдшоу. Див. Автоперехід.
autoAdvanceIntervalnumber5000Скільки кожне зображення лишається на екрані під час слайдшоу, у мілісекундах, з моменту його завантаження
showDownloadbooleanfalseДодає кнопку завантаження для активного елемента. Див. Завантаження.
apiRefMutableRefObject<ReelApi>-Ref для доступу до API Reel
renderControls(props: ControlsRenderProps) => ReactNode-Власні елементи керування, замінюють стандартну кнопку закриття, лічильник і перемикач повного екрана
renderNavigation(props: NavigationRenderProps) => ReactNode-Власна навігація, замінює стандартні стрілки вперед і назад
renderInfo(props: InfoRenderProps) => ReactNode-Власний інформаційний оверлей, замінює стандартний градієнт із заголовком та описом. Поверніть null, щоб сховати.
renderSlide(props: SlideRenderProps) => ReactNode | null-Власний рендеринг слайда. Отримує { item, index, size, isActive, autoAdvance, onReady, onWaiting, onError, onEnded }. Поверніть null, щоб лишити стандартний.
renderLoading(props: { item: LightboxItem; activeIndex: number }) => ReactNode-Власний індикатор завантаження, замінює стандартний
renderError(props: { item: LightboxItem; activeIndex: number }) => ReactNode-Власний індикатор помилки, замінює стандартний значок

Пропси LightboxUrlOverlay

LightboxUrlOverlayProps

Приймає всі візуальні та поведінкові пропси вище, крім isOpen, and replaces it with a controller. initialIndex тут ігнорується — слайд обирає position контролера, тож передане поруч значення перезаписувалося б на кожному відкритті.

PropТипТипове значенняОпис
controllerUrlStateControllerrequiredКонтролер із useOverlayUrlState. Його position вирішує, чи оверлей відкритий і який слайд показує; оверлей записує через нього назад на зміну слайда та на закриття.

Колбеки

PropТипОпис
onClose() => voidВикликається, коли Lightbox закривається. Обов’язковий у LightboxOverlay (стан відкриття ваш, тож закриття обробляєте ви); необов’язковий у LightboxUrlOverlay, де закриттям керує URL — передавайте лише щоб зреагувати після закриття.
onSlideChange(index: number) => voidВикликається після зміни слайда

Пропси Reel (передані далі)

Ці пропси передаються далі в компонент Reel. Їхній тип — ReelProxyProps, підмножина пропсів Reel, яку оверлей передає далі; типізуйте ним обгортку, що їх прокидає.

PropТипТипове значенняОпис
loopbooleanfalseВмикає нескінченний цикл
enableNavKeysbooleantrueВмикає навігацію з клавіатури
enableWheelbooleantrueВмикає навігацію колесом миші
wheelDebounceMsnumber200Тривалість дебаунсу колеса (мс)
transitionDurationnumber300Тривалість анімації переходу (мс)
swipeDistanceFactornumber0.12Поріг свайпу (0–1)
swipeToCloseDirection'up' | 'down''up'Напрямок жесту свайпу для закриття на мобільних

Types

LightboxItem

typescript
interface LightboxItem {
  src: string;
  type?: 'image' | 'video';  // defaults to 'image'
  poster?: string;            // thumbnail for video items
  title?: string;
  description?: string;
  width?: number;
  height?: number;
  downloadUrl?: string;       // what the download button saves; defaults to src
  downloadName?: string;      // file name; defaults to the URL's last segment
}

ControlsRenderProps

typescript
interface ControlsRenderProps {
  item: LightboxItem;
  activeIndex: number;
  count: number;
  isFullscreen: boolean;
  isAutoAdvancing: boolean;
  isDownloading: boolean;
  onClose: () => void;
  onToggleFullscreen: () => void;
  onToggleAutoAdvance?: () => void; // only with autoAdvance
  onDownload?: () => void;          // only with showDownload
}
typescript
interface NavigationRenderProps {
  item: LightboxItem;
  activeIndex: number;
  count: number;
  onPrev: () => void;
  onNext: () => void;
}

SlideRenderProps

typescript
interface SlideRenderProps {
  item: LightboxItem;
  index: number;
  size: [number, number];
  isActive: boolean;
  autoAdvance: boolean; // a video should play once, not loop
  onReady: () => void;
  onWaiting: () => void;
  onError: () => void;
  onEnded: () => void;  // a video played to its end
}

InfoRenderProps

typescript
interface InfoRenderProps {
  item: LightboxItem;
  index: number;
}

Sub-Components

Повторно використовувані підкомпоненти для складання власних елементів керування через renderControls.

CloseButton

Стандартна кнопка закриття у вигляді хрестика. Пропси (CloseButtonProps): onClick, необов’язкові className і style.

tsx
import { CloseButton } from '@reelkit/react-lightbox';

<CloseButton onClick={onClose} />

Counter

Значок лічильника зображень на кшталт «1 / 3». Пропси (CounterProps): currentIndex (від нуля), count, необов’язкові className і style.

tsx
import { Counter } from '@reelkit/react-lightbox';

<Counter currentIndex={activeIndex} count={count} />

FullscreenButton

Кнопка перемикання повного екрана (іконка Maximize або Minimize). Пропси (FullscreenButtonProps): isFullscreen, onToggle, необов’язкові className і style.

tsx
import { FullscreenButton } from '@reelkit/react-lightbox';

<FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />

SoundButton

Кнопка перемикання звуку для слайдів-відео (іконка Volume2 або VolumeX). Автоматично входить у renderControls from useVideoSlideRenderer. Для окремого використання у власних елементах керування доступ до стану звуку — через useSoundState. Пропси (SoundButtonProps): isMuted, onToggle, необов’язкові className і style.

tsx
import { SoundButton } from '@reelkit/react-lightbox';
import { useSoundState } from '@reelkit/react';

// Inside a component wrapped in SoundProvider:
function CustomControls({ onClose }) {
  const soundState = useSoundState();

  return (
    <div>
      <SoundButton
        isMuted={soundState.muted.value}
        onToggle={soundState.toggle}
      />
      <button onClick={onClose}>Close</button>
    </div>
  );
}

PlayPauseButton

Перемикач слайдшоу (іконка Play або Pause, aria-pressed під час відтворення). Пропси (PlayPauseButtonProps): isPlaying, onToggle, необов’язкові className і style. Стандартні елементи керування показують її, коли autoAdvance увімкнено.

tsx
import {
  LightboxOverlay,
  PlayPauseButton,
  CloseButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  autoAdvance
  renderControls={({ isAutoAdvancing, onToggleAutoAdvance, onClose }) => (
    <>
      {onToggleAutoAdvance && (
        <PlayPauseButton
          isPlaying={isAutoAdvancing}
          onToggle={onToggleAutoAdvance}
        />
      )}
      <CloseButton onClick={onClose} />
    </>
  )}
  {...props}
/>;

DownloadButton

Кнопка завантаження (іконка Download), вимкнена, поки isDownloading. Пропси (DownloadButtonProps): isDownloading, onClick, необов’язкові className і style. Стандартні елементи керування показують її, коли showDownload увімкнено.

tsx
import {
  LightboxOverlay,
  DownloadButton,
  CloseButton,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  showDownload
  renderControls={({ isDownloading, onDownload, onClose }) => (
    <>
      {onDownload && (
        <DownloadButton isDownloading={isDownloading} onClick={onDownload} />
      )}
      <CloseButton onClick={onClose} />
    </>
  )}
  {...props}
/>;

Hooks

useVideoSlideRenderer

Хук для підтримки відео за бажанням. Повертає renderSlide, renderControls, та SoundProvider — загорніть оверлей у SoundProvider і передайте функції рендерингу. Тип результату — UseVideoSlideRendererResult, який також містить hasVideo: true, коли хоча б один елемент має type: 'video'.

Його renderSlide малює відео-елементи через LightboxVideoSlide — слайд-відео, що грає через один спільний елемент <video>, тож звук працює й на iOS, і запам’ятовує позицію кожного відео. Рендерте його самостійно лише з власного renderSlide. Його пропси (LightboxVideoSlideProps): src, poster, isActive, size, slideKey (ключ для збереженої позиції та захопленого кадру), loop (типово true) і колбеки onPlaying, onWaiting, onError, onEnded.

typescript
import { useVideoSlideRenderer } from '@reelkit/react-lightbox';

const { renderSlide, renderControls, SoundProvider, hasVideo } =
  useVideoSlideRenderer(items);

// SoundProvider  — wrap LightboxOverlay in this for mute/unmute support
// renderSlide    — pass to LightboxOverlay's renderSlide prop
// renderControls — pass to LightboxOverlay's renderControls prop
//                  (includes Counter, FullscreenButton, SoundButton, CloseButton)
// hasVideo       — true if items contain at least one video

useFullscreen

Хук для керування станом повного екрана з кросбраузерною підтримкою.

tsx
import { useRef } from 'react';
import { useFullscreen } from '@reelkit/react';

function CustomLightbox() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isFullscreen, requestFullscreen, exitFullscreen, toggleFullscreen] =
    useFullscreen({ ref: containerRef });

  return (
    <div ref={containerRef}>
      <button onClick={toggleFullscreen}>
        {isFullscreen.value ? 'Exit Fullscreen' : 'Enter Fullscreen'}
      </button>
    </div>
  );
}

Переходи

Передайте будь-яку TransitionTransformFn via the transitionFn Якщо імпортувати лише той перехід, який використовуєте, решту збирач прибере через tree-shaking. Типово — slideTransition , коли не задано.

FunctionFromОпис
slideTransition@reelkit/react-lightboxЗвичайний горизонтальний зсув (типово)
lightboxFadeTransition@reelkit/react-lightboxПлавне перетікання між зображеннями
flipTransition@reelkit/react-lightboxЕфект 3D-перевороту картки
lightboxZoomTransition@reelkit/react-lightboxНаближення від меншого до звичайного розміру
tsx
import {
  LightboxOverlay,
  lightboxFadeTransition,
} from '@reelkit/react-lightbox';

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  initialIndex={0}
  onClose={handleClose}
  transitionFn={lightboxFadeTransition}
/>

Власна функція переходу

Напишіть власну TransitionTransformFn and pass it via transitionFn. Сигнатура повторює переходи слайдера в ядрі.

tsx
import { LightboxOverlay } from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';

const customFade: TransitionTransformFn = (offset, size) => ({
  transform: `translate3d(${offset * size[0]}px, 0, 0)`,
  opacity: 1 - Math.min(Math.abs(offset), 1),
});

<LightboxOverlay
  isOpen={isOpen}
  images={images}
  transitionFn={customFade}
  onClose={() => setIsOpen(false)}
/>

Класи CSS

Усі елементи інтерфейсу використовують звичайні класи CSS (не CSS-модулі), які можна перекрити селекторами вищої специфічності в таблиці стилів, підключеній після @reelkit/react-lightbox/styles.css. Для змін кольору, розміру та z-index краще беріть власні властивості CSS, описані в розділі Theming нижче.

ClassComponentОпис
.rk-lightbox-overlayOverlayКореневий контейнер (повноекранне тло)
.rk-lightbox-spinnerOverlayСтандартний індикатор завантаження
.rk-lightbox-img-errorOverlayКонтейнер стану помилки (зіпсоване зображення чи відео)
.rk-lightbox-img-error-textOverlayТекст стану помилки
.rk-lightbox-swipe-hintOverlayПідказка про свайп на мобільних
.rk-lightbox-controls-leftControlsКонтейнер елементів керування вгорі ліворуч
.rk-lightbox-btnControlsКнопки керування (повний екран тощо)
.rk-lightbox-play-pauseControlsКнопка відтворення й паузи слайдшоу (разом із .rk-lightbox-btn)
.rk-lightbox-downloadControlsКнопка завантаження (разом із .rk-lightbox-btn)
.rk-lightbox-closeControlsКнопка закриття
.rk-lightbox-counterControlsЗначок лічильника зображень
.rk-lightbox-navНавігаціяСтрілки навігації (обидві)
.rk-lightbox-nav-prevНавігаціяСтрілка назад
.rk-lightbox-nav-nextНавігаціяСтрілка вперед
.rk-lightbox-infoInfoКонтейнер заголовка й опису
.rk-lightbox-titleInfoЗаголовок зображення
.rk-lightbox-descriptionInfoОпис зображення
.rk-lightbox-slideSlideКонтейнер слайда
.rk-lightbox-imgSlideЕлемент зображення
.rk-lightbox-video-containerVideoSlideКонтейнер слайда-відео (за бажанням)
.rk-lightbox-video-elementVideoSlideЕлемент відео (за бажанням)
.rk-lightbox-video-posterVideoSlideПостер відео (за бажанням)

Theming

Кожен колір, розмір, z-index і перехід живе у власній властивості CSS. Перевизначайте одну чи кілька на :root (або на будь-якому предку Lightbox), щоб змінити тему, не чіпаючи код компонентів.

TokenТипове значенняControls
--rk-lightbox-overlay-bg#000Колір повноекранного фону
--rk-lightbox-overlay-z9999z-index оверлею
--rk-lightbox-top-shade-height80pxВисота верхнього градієнтного затемнення
--rk-lightbox-top-shade-bglinear-gradient(rgba(0,0,0,0.6), transparent)Колір верхнього градієнтного затемнення
--rk-lightbox-edge-padding16pxВідступ від краю для кнопок закриття, навігації та елементів керування зліва вгорі
--rk-lightbox-controls-gap12pxВідстань між елементами керування зліва вгорі
--rk-lightbox-transition0.2sТривалість переходу кнопки під час наведення
--rk-lightbox-blur8pxРадіус розмиття фону для кнопок і плашок
--rk-lightbox-btn-bgrgba(0, 0, 0, 0.5)Типовий фон кнопок закриття, навігації та малих кнопок
--rk-lightbox-btn-bg-hoverrgba(255, 255, 255, 0.2)Фон кнопок закриття, навігації та малих кнопок під час наведення
--rk-lightbox-btn-fg#fffКолір іконок кнопок закриття, навігації та малих кнопок
--rk-lightbox-btn-size36pxРозмір малої кнопки (перемикач повноекранного режиму тощо)
--rk-lightbox-btn-disabled-opacity0.5Непрозорість вимкненої малої кнопки (під час завантаження)
--rk-lightbox-close-size40pxРозмір кнопки закриття
--rk-lightbox-nav-size48pxРозмір стрілок «назад» і «далі»
--rk-lightbox-nav-opacity0.7Непрозорість стрілок «назад» і «далі» у спокої
--rk-lightbox-counter-fg#fffКолір тексту лічильника
--rk-lightbox-counter-bgrgba(0, 0, 0, 0.5)Фон плашки лічильника
--rk-lightbox-counter-size14pxРозмір шрифту лічильника
--rk-lightbox-counter-padding6px 12pxВнутрішній відступ плашки лічильника
--rk-lightbox-counter-radius20pxРадіус заокруглення плашки лічильника
--rk-lightbox-spinner-size28pxТипова ширина й висота спінера
--rk-lightbox-spinner-trackrgba(255, 255, 255, 0.2)Колір доріжки спінера
--rk-lightbox-spinner-fg#fffКолір індикатора спінера
--rk-lightbox-spinner-duration0.8sТривалість обертання спінера
--rk-lightbox-error-fgrgba(255, 255, 255, 0.4)Колір іконки й тексту помилки
--rk-lightbox-error-text-size13pxРозмір шрифту повідомлення про помилку
--rk-lightbox-info-bglinear-gradient(transparent, rgba(0,0,0,0.8))Градієнт затемнення під підписом
--rk-lightbox-info-padding24pxВнутрішній відступ підпису
--rk-lightbox-title-size18pxРозмір шрифту заголовка
--rk-lightbox-description-size14pxРозмір шрифту опису
--rk-lightbox-info-fg#fffКолір тексту підпису
--rk-lightbox-hint-fgrgba(255, 255, 255, 0.5)Колір тексту підказки про свайп
--rk-lightbox-hint-bgrgba(0, 0, 0, 0.3)Фон плашки підказки про свайп
--rk-lightbox-hint-duration3sЗагальна тривалість появи та згасання підказки про свайп
--rk-lightbox-video-bg#000Фон смуг навколо <video>

Вставте фрагмент нижче в таблицю стилів, підключену після @reelkit/react-lightbox/styles.css.

css
/* Brand the lightbox */
:root {
  --rk-lightbox-overlay-bg: #0f172a;
  --rk-lightbox-btn-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-btn-bg-hover: rgba(168, 85, 247, 0.85);
  --rk-lightbox-nav-size: 56px;
  --rk-lightbox-counter-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-info-bg: linear-gradient(
    transparent,
    rgba(99, 102, 241, 0.55) 60%,
    rgba(168, 85, 247, 0.85)
  );
}

Accessibility

Корінь оверлея — модальний діалог (role="dialog", aria-modal="true"). Set ariaLabel щоб змінити оголошення для екранного читача; типове значення — «Image gallery». Кожен слайд несе role="group", aria-roledescription="slide", та aria-label="Зображення N з M".

Lightbox захоплює фокус під час відкриття й повертає його на елемент-тригер після закриття. Tab і Shift+Tab циклічно проходять фокусовані елементи всередині; фокус, що вислизнув (клік поза Lightbox, програмна установка), повертається назад. Реалізовано через captureFocusForReturn та createFocusTrap from @reelkit/core.

Клавіатурні скорочення

KeyAction
ArrowLeftПопереднє зображення
ArrowRightНаступне зображення
EscapeЗакрити Lightbox (або вийти з повноекранного режиму, якщо він активний)