feat(a11y,i18n): more modal focus-traps + Telegram theme/language sync

Modal a11y:
- focus-trap + role=dialog/aria-modal/aria-labelledby on AdminRoles delete confirm
  and AdminBanSystem user-detail modal (+ close aria-label)

Telegram theme/language sync (first run only, explicit choice always wins):
- getTelegramColorScheme() / getTelegramLanguageCode() helpers in useTelegramSDK
- useTheme initial state follows the Telegram client color scheme when no stored theme
- applyTelegramLanguage() adopts the Telegram client language when no stored choice,
  called from main.tsx after SDK init (launch params unavailable before init)
This commit is contained in:
c0mrade
2026-05-25 23:31:38 +03:00
parent 8e0b63bac8
commit 0caba3dffb
6 changed files with 96 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ import {
expandViewport,
retrieveLaunchParams,
retrieveRawInitData,
themeParamsState,
} from '@telegram-apps/sdk-react';
const FULLSCREEN_CACHE_KEY = 'cabinet_fullscreen_enabled';
@@ -66,6 +67,46 @@ export function getTelegramInitData(): string | null {
}
}
function isDarkHexColor(hex: string): boolean {
const m = hex.replace('#', '');
const full = m.length === 3 ? m.replace(/(.)/g, '$1$1') : m;
if (full.length !== 6) return false;
const r = parseInt(full.slice(0, 2), 16);
const g = parseInt(full.slice(2, 4), 16);
const b = parseInt(full.slice(4, 6), 16);
// Perceived sRGB luminance; below 0.5 reads as a dark surface.
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 < 0.5;
}
/**
* The Telegram client's effective color scheme ('light' | 'dark'), derived from
* the theme background color. Returns null outside Telegram or before theme params load.
*/
export function getTelegramColorScheme(): 'light' | 'dark' | null {
if (!detectTelegram()) return null;
try {
const bg = themeParamsState()?.bgColor;
return bg ? (isDarkHexColor(bg) ? 'dark' : 'light') : null;
} catch {
return null;
}
}
/**
* The user's Telegram client language as a 2-letter code (e.g. 'en'), or null
* outside Telegram / when unavailable.
*/
export function getTelegramLanguageCode(): string | null {
if (!detectTelegram()) return null;
try {
const user = retrieveLaunchParams().tgWebAppData?.user as { languageCode?: string } | undefined;
const code = user?.languageCode;
return code ? code.split('-')[0].toLowerCase() : null;
} catch {
return null;
}
}
export type TelegramPlatform =
| 'android'
| 'ios'

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import { EnabledThemes, DEFAULT_ENABLED_THEMES } from '../types/theme';
import { themeColorsApi } from '../api/themeColors';
import { STORAGE_KEYS } from '../config/constants';
import { getTelegramColorScheme } from './useTelegramSDK';
type Theme = 'dark' | 'light';
@@ -74,6 +75,13 @@ export function useTheme() {
if (stored && !enabled[stored]) {
return enabled.dark ? 'dark' : 'light';
}
// No stored preference: follow the Telegram client's color scheme in a Mini App.
if (!stored) {
const tgScheme = getTelegramColorScheme();
if (tgScheme && enabled[tgScheme]) {
return tgScheme;
}
}
// Check system preference
if (window.matchMedia('(prefers-color-scheme: light)').matches && enabled.light) {
return 'light';