feat: modal windows

This commit is contained in:
2026-08-14 19:49:27 +07:00
parent 3d8b78ec52
commit 76f26430ba
13 changed files with 377 additions and 16 deletions

View File

@@ -0,0 +1,77 @@
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import QRCode from 'qrcode';
interface SubscriptionQrModalProps {
link: string | null;
label?: string;
imageAlt?: string;
onClose: () => void;
}
export function SubscriptionQrModal({
link,
label = 'Ссылка подписки',
imageAlt = 'QR-код ссылки подписки',
onClose,
}: SubscriptionQrModalProps) {
const [image, setImage] = useState<string | null>(null);
useEffect(() => {
if (!link) return;
let active = true;
void QRCode.toDataURL(link, {
errorCorrectionLevel: 'M',
margin: 2,
width: 280,
color: { dark: '#1a1610', light: '#faf7f2' },
})
.then((dataUrl) => {
if (active) setImage(dataUrl);
})
.catch(() => {
if (active) setImage(null);
});
const onKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') onClose();
};
document.addEventListener('keydown', onKey);
document.body.style.overflow = 'hidden';
return () => {
active = false;
document.removeEventListener('keydown', onKey);
document.body.style.overflow = '';
};
}, [link, onClose]);
if (!link) return null;
return createPortal(
<div
className="modal-veil is-open"
role="dialog"
aria-modal="true"
aria-labelledby="subscription-qr-title"
onClick={(event) => {
if (event.target === event.currentTarget) onClose();
}}
>
<div className="modal subscription-qr-modal" role="document">
<div className="modal-kicker">{label}</div>
<h3 className="modal-title" id="subscription-qr-title">
Откройте на устройстве
</h3>
<p className="modal-body">Отсканируйте код камерой или приложением Malenia.</p>
<div className="subscription-qr-image">
{image ? <img src={image} alt={imageAlt} /> : <span>Генерируем QR-код</span>}
</div>
<button type="button" className="btn" onClick={onClose} autoFocus>
Закрыть
</button>
</div>
</div>,
document.body,
);
}