mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
feat(a11y): cross-platform hardening + modal focus-trap from impeccable audit
Responsive / viewport: - add .min-h-viewport / .h-viewport utilities (100dvh + Telegram --tg-viewport-stable-height) - migrate min-h-screen / h-screen / 100vh across 18 files - reveal hover-only controls on focus-within + touch (desktop nav, admin settings) - grid breakpoints (TopUpAmount, Contests), larger touch targets, nav aria-labels Platform routing (Telegram WebView correctness): - clipboard -> copyToClipboard util with execCommand fallback (10 files) - window.open -> openTelegramLink / openLink (Profile, Referral, ChannelSubscriptionScreen) - window.confirm -> useNativeDialog (admin pages); PromoOffersSection -> useDestructiveConfirm - window.prompt -> usePrompt / PromptDialogHost (TipTap link editors) - ESLint no-restricted-properties guard against regressions (adapters/clipboard util exempt) Modal accessibility: - new useFocusTrap hook (focus trap, Esc, scroll lock, focus restore) - role=dialog / aria-modal / focus-trap: Polls, SuccessNotificationModal, AdminPolicies, AdminPromoGroups, AdminCampaigns, BroadcastPreview (Telegram + Email) - new global usePrompt store + PromptDialogHost
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { pollsApi, PollInfo, PollQuestion } from '../api/polls';
|
||||
import { useFocusTrap } from '../hooks/useFocusTrap';
|
||||
|
||||
const ClipboardIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
@@ -107,6 +108,10 @@ export default function Polls() {
|
||||
setCompletionMessage(null);
|
||||
};
|
||||
|
||||
const pollDialogRef = useFocusTrap<HTMLDivElement>(!!selectedPoll, {
|
||||
onEscape: handleClosePoll,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-64 items-center justify-center">
|
||||
@@ -133,10 +138,28 @@ export default function Polls() {
|
||||
{/* Poll Modal */}
|
||||
{selectedPoll && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="card max-h-[80vh] w-full max-w-lg overflow-y-auto">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/60"
|
||||
onClick={handleClosePoll}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
ref={pollDialogRef}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="poll-dialog-title"
|
||||
tabIndex={-1}
|
||||
className="card relative max-h-[80vh] w-full max-w-lg overflow-y-auto"
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold">{selectedPoll.title}</h2>
|
||||
<button onClick={handleClosePoll} className="text-dark-400 hover:text-dark-200">
|
||||
<h2 id="poll-dialog-title" className="text-xl font-bold">
|
||||
{selectedPoll.title}
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleClosePoll}
|
||||
aria-label={t('common.close')}
|
||||
className="text-dark-400 hover:text-dark-200"
|
||||
>
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
|
||||
Reference in New Issue
Block a user