Lightbox
Lightbox
캐러셀 내비게이션이 있는 전체 화면 미디어 라이트박스예요. @mantine/lightbox 패키지로 제공되며 MIT 라이선스로 배포돼요.
출처: 문서
본문
설치
yarn add embla-carousel@^8.5.2 embla-carousel-react@^8.5.2 @mantine/lightbox
설치 후 애플리케이션의 루트에서 패키지 스타일을 import 해요:
import '@mantine/core/styles.css';
import '@mantine/lightbox/styles.css';
사용법
@mantine/lightbox는 embla carousel 위에 만든 전체 화면 미디어 라이트박스예요. 아무 이미지나 클릭하면 라이트박스가 열려요:
import '@mantine/lightbox/styles.css';
import { useState } from 'react';
import { Image, SimpleGrid } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const images = [
'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png',
'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-2.png',
'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-3.png',
'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-4.png',
'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-5.png',
];
const slides: LightboxSlideData[] = images.map((src) => ({ src }));
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox
opened={opened}
onClose={() => setOpened(false)}
slides={slides}
currentIndex={index}
onIndexChange={setIndex}
/>
<SimpleGrid cols={3}>
{images.map((src, i) => (
<Image
key={src}
src={src}
onClick={() => {
setIndex(i);
setOpened(true);
}}
/>
))}
</SimpleGrid>
</>
);
}
Slide 데이터
slides prop은 LightboxSlideData 객체 배열을 받아요. slide 타입은 세 가지예요 – 이미지(기본), 비디오, 커스텀:
import type { LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
// Image slide (default type)
{
src: 'image.png', // Image URL
alt: 'Mountain lake', // Alt text, always set it for screen readers
caption: 'Mountain lake at sunrise', // Caption displayed below the slide
thumbSrc: 'image-small.png', // Custom thumbnail URL, `src` is used if not set
renderThumb: ({ active }) => Thumb, // Custom thumbnail content, see below
srcSet: 'image-2x.png 2x', // Optional srcset for responsive images
sizes: '(max-width: 600px) 100vw, 50vw', // Optional sizes attribute
loading: 'lazy', // Optional loading attribute, see below
},
// Video slide
{
type: 'video',
src: 'video.mp4',
label: 'Product demo', // Accessible video label
poster: 'poster.png', // Poster image, also used as thumbnail
thumbSrc: 'poster-small.png', // Custom thumbnail URL, `poster` is used if not set
renderThumb: ({ active }) => Thumb, // Custom thumbnail content, see below
autoPlay: true, // Auto-play when the slide becomes active
tracks: [{ src: 'captions.vtt', kind: 'captions', srcLang: 'en', label: 'English' }],
},
// Custom slide
{
type: 'custom',
render: ({ active }) =>
<div>Custom content</div>,
thumbSrc: 'thumb.png', // Custom thumbnail URL
renderThumb: ({ active }) => Thumb, // Custom thumbnail content, see below
},
];
이미지 로딩
모든 slide가 한 번에 마운트되므로 이미지는 기본적으로 지연(lazy) 로딩돼요 – 활성 slide만 loading="eager"를 사용하고, 다른 모든 slide는 loading="lazy"를 사용해 브라우저가 뷰에 들어올 때 다운로드해요. 이렇게 많은 slide를 가진 갤러리를 열어도 모든 이미지를 한 번에 다운로드하지 않아요. Thumbnail은 항상 지연돼요 – thumbSrc는 src로 폴백하므로 원본이 크다면 더 작은 이미지로 설정해요.
이미지 slide에 loading 속성을 설정해 slide별로 재정의할 수 있어요. 예를 들어 처음 열리는 slide 옆에 있는 slide를 즉시(eager) 프리로드하려면:
import type { LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{ src: 'image-1.png', alt: 'First' },
{ src: 'image-2.png', alt: 'Second', loading: 'eager' },
];
내비게이션과 닫기 동작
내장 상호작용을 제어하려면 다음 props를 사용해요:
-
withNavigation– 이전/다음 화살표 버튼을 보여줘요, 기본true -
loop– 무한 루프 내비게이션을 활성화해요, 기본false -
closeOnClickOutside– slide 콘텐츠 주변의 빈 공간을 클릭하면 라이트박스를 닫아요, 기본false -
closeOnSwipeDown– 모바일에서 아래로 스와이프하면 닫아요, 기본true -
withKeyboardEvents– 키보드 단축키(화살표,F/T/Z)를 활성화해요, 기본true;Escape는 항상 라이트박스를 닫아요 -
returnFocus– 라이트박스가 닫힐 때 마지막 활성 요소로 포커스를 되돌려요, 기본true -
withInitialFocusPlaceholder– 라이트박스 콘텐츠 시작 부분에 숨겨진 focus 가능한 요소를 추가해 포인터로 열 때 첫 번째 툴바 버튼이 보이는 포커스를 받지 않게 해요, 기본true
Zoom
withZoom prop으로 이미지 zoom을 활성화해요. 데스크톱에서는 이미지를 클릭해 확대, 스크롤 휠로 확대 수준 조절, 줌 상태에서 드래그나 화살표 키로 팬을 해요. 모바일에서는 더블 탭으로 확대, 핀치로 조절해요. zoomMaxScale로 최대 zoom 배율을 바꿀 수 있어요(기본 3):
import { useState } from 'react';
import { Image, SimpleGrid } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const images = [ 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' ];
const slides: LightboxSlideData[] = images.map((src) => ({ src }));
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox
opened={opened}
onClose={() => setOpened(false)}
slides={slides}
currentIndex={index}
onIndexChange={setIndex}
withZoom
/>
{/* images grid */}
</>
);
}
Thumbnails
withThumbnails로 하단 thumbnail 스트립을 활성화해요. thumbnail을 클릭해 해당 slide로 이동해요. T 키보드 단축키나 툴바 버튼으로 런타임에 표시 여부를 전환해요 – 스트립은 transitionDuration prop 값으로 애니메이션돼요:
import { useState } from 'react';
import { Image, SimpleGrid } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const images = [ 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' ];
const slides: LightboxSlideData[] = images.map((src) => ({ src }));
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox
opened={opened}
onClose={() => setOpened(false)}
slides={slides}
currentIndex={index}
onIndexChange={setIndex}
withThumbnails
/>
{/* images grid */}
</>
);
}
커스텀 thumbnails
기본적으로 thumbnail은 요소로 렌더링돼요: 이미지 slide는 `thumbSrc` 또는 `src`를, 비디오 slide는 `thumbSrc` 또는 `poster`를, 커스텀 slide는 `thumbSrc`를 사용해요. 어떤 slide에든 `renderThumb`을 설정해 커스텀 thumbnail 콘텐츠를 대신 렌더링해요 – 예를 들어 thumbnail이 이미지가 아니라 작은 비디오라면 요소를요. 함수는 { active } payload를 받으며, active는 현재 slide의 thumbnail에 대해 true예요. 렌더링된 콘텐츠는 64x64px 버튼 안에 배치되며, width: 100%와 height: 100%로 크기를 조절해요:
import type { LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{
type: 'video',
src: 'video.mp4',
renderThumb: () => (
<video src="video.mp4" muted style={{ width: '100%', height: '100%' }} />
),
},
];
모든 기능
완전한 경험을 위해 withZoom, withThumbnails, withFullscreen, withDownload를 결합해요:
import { useState } from 'react';
import { Image, SimpleGrid } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const images = [ 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' ];
const slides: LightboxSlideData[] = images.map((src) => ({ src }));
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox
opened={opened}
onClose={() => setOpened(false)}
slides={slides}
currentIndex={index}
onIndexChange={setIndex}
withZoom
withThumbnails
withFullscreen
withDownload
/>
{/* images grid */}
</>
);
}
루프 내비게이션
slide 목록 끝에서 무한 감싸기를 활성화하려면 loop를 설정해요:
import { useState } from 'react';
import { Button } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' },
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-2.png' },
];
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} currentIndex={index} onIndexChange={setIndex} loop />
<Button onClick={() => setOpened(true)}>Open lightbox with loop</Button>
</>
);
}
Slide 전환
기본적으로 프로그래매틱 내비게이션(화살표 버튼, 키보드, 제어된 인덱스 변경)은 즉시 스냅돼요. withSlideTransition을 설정해 slide 변경에 애니메이션을 적용해요. 애니메이션은 embla가 처리해요 – emblaOptions={{ duration: 40 }}로 속도를 바꿔요(embla duration은 밀리초가 아니며 20~60 값이 권장돼요):
import { useState } from 'react';
import { Button } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' },
];
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} currentIndex={index} onIndexChange={setIndex} withSlideTransition withThumbnails />
<Button onClick={() => setOpened(true)}>Open lightbox with slide transition</Button>
</>
);
}
Embla 옵션
캐러셀 동작은 emblaOptions prop으로 커스터마이즈할 수 있어요 – 기본 embla carousel 인스턴스에 직접 전달돼요. 예를 들어 드래그 동작이나 스크롤 애니메이션 속도를 바꿀 수 있어요.
loop와 startIndex는 loop와 currentIndex props로 관리되며 emblaOptions로 설정할 수 없어요. watchDrag 콜백은 여전히 호출되지만, 드래그는 이미지가 zoom된 동안 항상 비활성화되어 zoom 제스처가 중단되지 않아요:
import { Lightbox } from '@mantine/lightbox';
function Demo() {
return (
<Lightbox
opened={false}
onClose={() => {}}
slides={[]}
emblaOptions={{ dragFree: false, duration: 30, align: 'center' }}
/>
);
}
z-index
zIndex는 오버레이와 콘텐츠 요소의 z-index를 제어해요, 기본 400:
import { Lightbox } from '@mantine/lightbox';
function Demo() {
return <Lightbox opened={false} onClose={() => {}} slides={[]} zIndex={1000} />;
}
열기/닫기 전환
기본적으로 오버레이는 페이드 인되고 콘텐츠는 팝 인돼요 – 페이드되면서 95%에서 100%로 스케일돼요. 콘텐츠의 애니메이션을 바꾸려면 transitionProps를 사용해요 – 오버레이는 항상 페이드돼요. transitionProps는 Transition 컴포넌트와 같은 옵션(transition, duration, timingFunction)을 받아요. transitionDuration을 지름길로 사용해 duration만 바꿀 수 있어요(기본 200):
import { useState } from 'react';
import { Button, Group, Select } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [ { src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' } ];
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
const [transition, setTransition] = useState('pop');
return (
<>
<Lightbox
opened={opened}
onClose={() => setOpened(false)}
slides={slides}
currentIndex={index}
onIndexChange={setIndex}
transitionProps={{ transition: transition as any, duration: 400 }}
/>
<Group>
<Select data={['pop', 'fade', 'slide-up']} value={transition} onChange={setTransition} />
<Button onClick={() => setOpened(true)}>Open lightbox</Button>
</Group>
</>
);
}
애니메이션 비활성화
transitionProps={{ duration: 0 }}를 설정해 애니메이션 없이 라이트박스를 즉시 열고 닫아요:
import { useState } from 'react';
import { Button } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [ { src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' } ];
function Demo() {
const [opened, setOpened] = useState(false);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} transitionProps={{ duration: 0 }} />
<Button onClick={() => setOpened(true)}>Open lightbox without animation</Button>
</>
);
}
스와이프로 닫기
모바일에서 아래로 스와이프하면 라이트박스가 닫혀요. 이것은 기본적으로 활성화돼요. 비활성화하려면 closeOnSwipeDown={false}를 설정해요:
import { useState } from 'react';
import { Button, Group } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [ { src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' } ];
function Demo() {
const [opened, setOpened] = useState(false);
const [swipeEnabled, setSwipeEnabled] = useState(true);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} closeOnSwipeDown={swipeEnabled} />
<Group>
<Button onClick={() => setOpened(true)}>Open lightbox</Button>
<Button onClick={() => setSwipeEnabled((v) => !v)} variant="default">
Swipe close: {swipeEnabled ? 'enabled' : 'disabled'}
</Button>
</Group>
</>
);
}
외부 클릭으로 닫기
closeOnClickOutside를 설정해 현재 slide 콘텐츠 주변의 빈 공간을 클릭하면 라이트박스를 닫아요 – 이미지, 비디오 또는 커스텀 slide 콘텐츠 클릭은 무시되고, 툴바, 내비게이션 버튼, 캡션, thumbnail 클릭도 무시돼요. 우발적 닫기를 방지하기 위해 기본적으로 비활성화돼 있어요:
import { useState } from 'react';
import { Button, Group } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [ { src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' } ];
function Demo() {
const [opened, setOpened] = useState(false);
const [closeOnClickOutside, setCloseOnClickOutside] = useState(true);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} closeOnClickOutside={closeOnClickOutside} />
<Group>
<Button onClick={() => setOpened(true)}>Open lightbox</Button>
<Button onClick={() => setCloseOnClickOutside((v) => !v)} variant="default">
Close on click outside: {closeOnClickOutside ? 'enabled' : 'disabled'}
</Button>
</Group>
</>
);
}
Store API
앱에 Lightbox.Provider를 한 번 마운트하고 정적 Lightbox 메서드(기본 lightbox store 동작의 별칭)로 어디서든 라이트박스를 열어요:
import { Lightbox } from '@mantine/lightbox';
// Open with slides and optional start index
Lightbox.open({ slides, startIndex: 2 });
// Close
Lightbox.close();
// Navigate
Lightbox.next();
Lightbox.prev();
Lightbox.setIndex(5);
import { Button, Group } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png', caption: 'Slide 1' },
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-3.png', caption: 'Slide 3' },
];
function Demo() {
return (
<Group>
<Button onClick={() => Lightbox.open({ slides })}>Open lightbox</Button>
<Button onClick={() => Lightbox.open({ slides, startIndex: 2 })}>Open at slide 3</Button>
</Group>
);
}
여러 라이트박스
기본적으로 Lightbox.Provider와 정적 Lightbox.open/close/next/prev/setIndex 메서드는 공유 lightboxStore를 사용해요. 여러 독립적인 라이트박스를 실행하려면 createLightbox로 그 store에 바인딩된 actions와 함께 격리된 store를 만들고 store prop에 전달해요:
import { Button } from '@mantine/core';
import { createLightbox, Lightbox } from '@mantine/lightbox';
const [productStore, productLightbox] = createLightbox();
function Demo() {
return (
<>
<Lightbox.Provider store={productStore} />
<Button onClick={() => productLightbox.open({ slides })}>Open product gallery</Button>
</>
);
}
컴포넌트에서 어떤 라이트박스 store의 상태를 구독하려면 useLightboxStore 훅을 사용해요:
import { lightboxStore, useLightboxStore } from '@mantine/lightbox';
function Demo() {
const { opened, currentIndex, slides } = useLightboxStore(lightboxStore);
return <div>Current slide: {currentIndex + 1}</div>;
}
비디오 slide
slide에 type: 'video'를 설정해 요소를 렌더링해요. slide에서 벗어나면 비디오는 자동으로 일시정지돼요. `poster` 이미지가 thumbnail로 사용되며, 다른 이미지를 쓰려면 `thumbSrc`를, 커스텀 thumbnail을 렌더링하려면 `renderThumb`을 설정해요(예: 음소거된 요소):
import { useState } from 'react';
import { Button } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{
type: 'video',
src: 'https://www.w3schools.com/html/mov_bbb.mp4',
poster: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-2.png',
caption: 'Play this video, then navigate to the next slide – it pauses automatically',
autoPlay: true,
},
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png', caption: 'Image slide' },
];
function Demo() {
const [opened, setOpened] = useState(false);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} withThumbnails />
<Button onClick={() => setOpened(true)}>Open lightbox with videos</Button>
</>
);
}
커스텀 slide
완전 커스텀 slide 콘텐츠에는 type: 'custom'과 render 함수를 설정해요. renderThumb 또는 thumbSrc로 thumbnail을 제공해요 – 커스텀 slide에는 기본 thumbnail이 없어요:
import { useState } from 'react';
import { Button, Center, Text } from '@mantine/core';
import { Lightbox, LightboxSlideData } from '@mantine/lightbox';
const slides: LightboxSlideData[] = [
{ src: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' },
{
type: 'custom',
render: ({ active }) => (
<Center h="100%">
<Text>{active ? 'This slide is active' : 'This slide is not active'}</Text>
</Center>
),
renderThumb: () => <div style={{ width: '100%', height: '100%' }}>Custom</div>,
caption: 'Custom slide with render function',
},
];
function Demo() {
const [opened, setOpened] = useState(false);
const [index, setIndex] = useState(0);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} currentIndex={index} onIndexChange={setIndex} withThumbnails />
<Button onClick={() => setOpened(true)}>Open lightbox with custom slides</Button>
</>
);
}
커스텀 툴바
toolbarItems prop으로 기본 툴바 항목을 재정의해요. 각 항목은 key, icon, label, onClick, 선택적 position('left' 또는 'right')을 가져요.
toolbarItems는 배열이거나 현재 라이트박스 상태와 핸들러를 받는 함수일 수 있어요 – 내부 상태에 의존하는 항목(예: thumbnails, fullscreen, zoom 토글)을 만들려면 함수 형태를 사용해요. 함수는 다음 payload를 받아요:
interface ToolbarItemsPayload {
slides: LightboxSlideData[];
currentIndex: number;
setIndex: (index: number) => void;
next: () => void;
prev: () => void;
close: () => void;
thumbnailsVisible: boolean;
toggleThumbnails: () => void;
isFullscreen: boolean;
toggleFullscreen: () => void;
zoomed: boolean;
toggleZoom: () => void;
}
일반적인 작업에는 내장 툴바 항목 팩토리를 사용해요:
import { useState } from 'react';
import { Button } from '@mantine/core';
import {
createCloseToolbarItem,
createDownloadToolbarItem,
createFullscreenToolbarItem,
createThumbnailsToolbarItem,
Lightbox,
LightboxSlideData,
ToolbarItem,
ToolbarItemsPayload,
} from '@mantine/lightbox';
const images = [ 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-1.png' ];
const slides: LightboxSlideData[] = images.map((src) => ({ src }));
function InfoIcon() {
return <span>ℹ️</span>;
}
// toolbarItems as a function receives the current lightbox state and handlers
const toolbarItems = (payload: ToolbarItemsPayload): ToolbarItem[] => [
// Built-in factories for common actions
createThumbnailsToolbarItem(payload.toggleThumbnails, payload.thumbnailsVisible, payload.labels),
createFullscreenToolbarItem(payload.toggleFullscreen, payload.isFullscreen, payload.labels),
createDownloadToolbarItem(images[payload.currentIndex], payload.labels),
// Fully custom toolbar item
{
key: 'info',
icon: <InfoIcon />,
label: 'Image info',
position: 'right',
onClick: () => {
// eslint-disable-next-line no-alert
alert(`Viewing image ${payload.currentIndex + 1} of ${payload.slides.length}`);
},
},
createCloseToolbarItem(payload.close, payload.labels),
];
function Demo() {
const [opened, setOpened] = useState(false);
return (
<>
<Lightbox opened={opened} onClose={() => setOpened(false)} slides={slides} withThumbnails toolbarItems={toolbarItems} />
<Button onClick={() => setOpened(true)}>Open lightbox with custom toolbar</Button>
</>
);
}
복합 컴포넌트
레이아웃을 완전히 제어하려면 하위 컴포넌트를 직접 조합해요:
import { Lightbox } from '@mantine/lightbox';
function CustomLightbox({ opened, onClose, slides }) {
return (
<Lightbox.Root opened={opened} onClose={onClose}>
<Lightbox.Toolbar />
<Lightbox.Slides>
{slides.map((slide, index) => (
<Lightbox.Slide key={index} slide={slide} />
))}
</Lightbox.Slides>
<Lightbox.Thumbnails />
</Lightbox.Root>
);
}
Lightbox.Thumbnails는 Lightbox.Root에 withThumbnails가 설정될 때만 렌더링된다는 점에 주의하세요.
사용 가능한 하위 컴포넌트:
-
Lightbox.Root— 오버레이, portal, 포커스 트랩, 스크롤 잠금, 키보드 처리 -
Lightbox.Toolbar— actions와 slide 카운터가 있는 상단 바 -
Lightbox.Slides— Embla carousel 래퍼 -
Lightbox.Slide— 개별 slide(이미지, 비디오, 또는 커스텀) -
Lightbox.Thumbnails— 하단 thumbnail 스트립 -
Lightbox.Navigation— prev/next 화살표 버튼 -
Lightbox.Caption— 활성 slide 아래의 텍스트 -
Lightbox.CloseButton— 독립형 닫기 버튼 -
Lightbox.Provider— store 모드 마운트 지점
Labels
라이트박스가 렌더링하는 모든 문자열은 labels prop에 정의돼요. 바꾸고 싶은 labels만 전달해요 – 나머지는 DEFAULT_LABELS로 내보내지는 기본 영어 값으로 폴백돼요:
import { Lightbox } from '@mantine/lightbox';
function Demo() {
return (
<Lightbox
opened={false}
onClose={() => {}}
slides={[]}
labels={{
lightboxLabel: 'Galerie',
slideLabel: (index, total) => `Bild ${index} von ${total}`,
slidesLabel: 'Bilder',
thumbnailLabel: (index) => `Zu Bild ${index} wechseln`,
previousSlideLabel: 'Vorheriges Bild',
nextSlideLabel: 'Nächstes Bild',
enterFullscreenLabel: 'Vollbild aktivieren',
exitFullscreenLabel: 'Vollbild beenden',
showThumbnailsLabel: 'Miniaturansichten anzeigen',
hideThumbnailsLabel: 'Miniaturansichten ausblenden',
downloadLabel: 'Herunterladen',
closeLabel: 'Schließen',
}}
/>
);
}
애플리케이션의 모든 라이트박스의 labels를 바꾸려면 Lightbox 컴포넌트의 default props에 labels를 설정해요.
접근성
-
포커스는 열렸을 때 라이트박스 안에 트랩되고, 닫힐 때 마지막 활성 요소로 돌아가요(
returnFocusprop) -
라이트박스가 열리면 첫 번째 툴바 버튼 대신 숨겨진 placeholder 요소로 포커스가 이동해, 포인터 사용자에게 focus ring이 표시되지 않아요(
withInitialFocusPlaceholderprop) -
콘텐츠 요소는
role="dialog"와aria-modal="true"를 가지며labels.lightboxLabel의 접근 가능한 이름을 가져요 –aria-label을 전달해 단일 라이트박스에 대해 재정의할 수 있어요 -
이미지 slide에 항상
alt속성을 설정해요 – 없으면 스크린 리더가 이미지를 장식용으로 취급해요; 비디오 slide에는label속성을 설정해요 -
모든 인터랙티브 요소는
aria-label속성을 가져요; 커스텀 툴바 항목에는label속성이 필수예요 -
aria-live="polite"영역이 slide 변경(현재 slide의alt/label포함)을 스크린 리더에 알려줘요 -
비활성 slide는
inert라서 콘텐츠가 스크린 리더에서 숨겨지고 탭 순서에서 제거되므로 현재 slide만 접근 가능해요 -
라이트박스가 열려 있으면 body 스크롤이 잠겨요
-
툴바에서 진입한 전체 화면 모드는 라이트박스가 닫힐 때 자동으로 종료돼요
키보드 단축키
Escape는 항상 라이트박스를 닫아요. 다른 단축키는 withKeyboardEvents가 설정되어 있을 때(기본) 활성화돼요. 라이트박스 어디든 input, textarea, 미디어 요소 안에 포커스가 있거나, slide 콘텐츠 안의 버튼, 링크 또는 다른 위젯에 포커스가 있을 때는 무시돼요 – 그래서 커스텀 slide의 컨트롤이 자신의 키보드 처리를 유지해요. F/T/Z는 해당 기능이 활성화되어 있을 때만 동작해요:
| Key | 설명 |
|---|---|
| Escape | 라이트박스 닫기 |
| ArrowLeft | 이전 slide, zoom 상태에서는 왼쪽으로 팬 |
| ArrowRight | 다음 slide, zoom 상태에서는 오른쪽으로 팬 |
| ArrowUp | zoom 상태에서 위로 팬 |
| ArrowDown | zoom 상태에서 아래로 팬 |
| F | 전체 화면 토글(withFullscreen 필요) |
| T | thumbnails 토글(withThumbnails 필요) |
| Z | zoom 토글(withZoom 필요) |