FeedLightbox.tsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. 'use client';
  2. import { useCallback, useEffect, useState } from 'react';
  3. import { createPortal } from 'react-dom';
  4. import { ChevronLeft, ChevronRight, X } from 'lucide-react';
  5. type Props = {
  6. urls: string[];
  7. startIndex: number;
  8. onClose: () => void;
  9. };
  10. export default function FeedLightbox({ urls, startIndex, onClose }: Props) {
  11. const [index, setIndex] = useState(startIndex);
  12. const [mounted, setMounted] = useState(false);
  13. useEffect(() => {
  14. setMounted(true);
  15. }, []);
  16. const next = useCallback(() => {
  17. setIndex((i) => (i + 1) % urls.length);
  18. }, [urls.length]);
  19. const prev = useCallback(() => {
  20. setIndex((i) => (i - 1 + urls.length) % urls.length);
  21. }, [urls.length]);
  22. useEffect(() => {
  23. const handleKey = (e: KeyboardEvent) => {
  24. if (e.key === 'Escape') {
  25. onClose();
  26. } else if (e.key === 'ArrowRight') {
  27. next();
  28. } else if (e.key === 'ArrowLeft') {
  29. prev();
  30. }
  31. };
  32. document.addEventListener('keydown', handleKey);
  33. document.body.style.overflow = 'hidden';
  34. return () => {
  35. document.removeEventListener('keydown', handleKey);
  36. document.body.style.overflow = '';
  37. };
  38. }, [next, prev, onClose]);
  39. if (!mounted || urls.length === 0) {
  40. return null;
  41. }
  42. const body = (
  43. <div className="feed-lightbox" role="dialog" aria-modal="true" onClick={onClose}>
  44. <button type="button" className="feed-lightbox__close" aria-label="닫기" onClick={onClose}>
  45. <X size={28} />
  46. </button>
  47. {urls.length > 1 && (
  48. <button type="button" className="feed-lightbox__nav feed-lightbox__nav--prev" aria-label="이전" onClick={(e) => { e.stopPropagation(); prev(); }}>
  49. <ChevronLeft size={32} />
  50. </button>
  51. )}
  52. <div className="feed-lightbox__stage" onClick={(e) => e.stopPropagation()}>
  53. <img src={urls[index]} alt={`이미지 ${index + 1}`} className="feed-lightbox__image" />
  54. {urls.length > 1 && (
  55. <div className="feed-lightbox__counter">{index + 1} / {urls.length}</div>
  56. )}
  57. </div>
  58. {urls.length > 1 && (
  59. <button type="button" className="feed-lightbox__nav feed-lightbox__nav--next" aria-label="다음" onClick={(e) => { e.stopPropagation(); next(); }}>
  60. <ChevronRight size={32} />
  61. </button>
  62. )}
  63. </div>
  64. );
  65. return createPortal(body, document.body);
  66. }