ImageLightbox.tsx 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. import { useEffect, type ReactNode } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. type ImageLightboxProps = {
  4. image: string | null
  5. alt?: string
  6. onClose: () => void
  7. action?: ReactNode
  8. }
  9. export function ImageLightbox({ image, alt = '', onClose, action }: ImageLightboxProps) {
  10. const { t } = useTranslation()
  11. const translated = (key: string, fallback: string, values?: Record<string, string>) => {
  12. const value = t(key, values)
  13. return value === key ? fallback : value
  14. }
  15. useEffect(() => {
  16. if (!image) return undefined
  17. const handleKeyDown = (event: KeyboardEvent) => {
  18. if (event.key === 'Escape') onClose()
  19. }
  20. document.addEventListener('keydown', handleKeyDown)
  21. const previousOverflow = document.body.style.overflow
  22. document.body.style.overflow = 'hidden'
  23. return () => {
  24. document.removeEventListener('keydown', handleKeyDown)
  25. document.body.style.overflow = previousOverflow
  26. }
  27. }, [image, onClose])
  28. if (!image) return null
  29. return (
  30. <div className="aitori-image-lightbox" role="dialog" aria-modal="true" aria-label={alt || translated('common.imagePreview', '图片预览')} onClick={onClose}>
  31. <button type="button" aria-label={translated('common.closeImagePreview', '关闭图片预览')} onClick={onClose}><i className="feather icon-feather-x"></i></button>
  32. <div className={`aitori-image-lightbox-content${action ? ' has-action' : ''}`} onClick={(event) => event.stopPropagation()}>
  33. <img src={image} alt={alt ? translated('common.enlargedImage', `${alt} 放大预览`, { name: alt }) : translated('common.enlargedImageFallback', '图片放大预览')} />
  34. {action ? <div className="aitori-image-lightbox-action">{action}</div> : null}
  35. </div>
  36. </div>
  37. )
  38. }