a11y: accessibility for mobile platforms (#2044)

This commit is contained in:
Huang Xin
2025-09-16 00:45:45 +08:00
committed by GitHub
parent dfc24f3803
commit 34f1af727f
35 changed files with 200 additions and 74 deletions
@@ -116,6 +116,7 @@
"Book Details": "تفاصيل الكتاب",
"From Local File": "من ملف محلي",
"TOC": "جدول المحتويات",
"Table of Contents": "جدول المحتويات",
"Book uploaded: {{title}}": "تم تحميل الكتاب: {{title}}",
"Failed to upload book: {{title}}": "فشل تحميل الكتاب: {{title}}",
"Book downloaded: {{title}}": "تم تنزيل الكتاب: {{title}}",
@@ -558,5 +559,7 @@
"Close": "إغلاق",
"Delete Book Options": "خيارات حذف الكتاب",
"ON": "تشغيل",
"OFF": "إيقاف"
"OFF": "إيقاف",
"Reading Progress": "تقدم القراءة",
"Page Margin": "هامش الصفحة"
}
@@ -323,6 +323,7 @@
"Match Whole Words": "পূর্ণ শব্দ মিলান",
"Match Diacritics": "ডায়াক্রিটিক্স মিলান",
"TOC": "সূচিপত্র",
"Table of Contents": "সূচিপত্র",
"Sidebar": "সাইডবার",
"Disable Translation": "অনুবাদ নিষ্ক্রিয়",
"Translation Not Available": "অনুবাদ উপলব্ধ নয়",
@@ -542,5 +543,7 @@
"Close": "বন্ধ করুন",
"Delete Book Options": "বই মুছে ফেলার অপশন",
"ON": "চালু",
"OFF": "বন্ধ"
"OFF": "বন্ধ",
"Reading Progress": "পড়ার অগ্রগতি",
"Page Margin": "পৃষ্ঠার মার্জিন"
}
@@ -117,6 +117,7 @@
"Book Details": "དཔེ་དེབ་ཀྱི་གནས་ཚུལ་རྒྱས་པ།",
"From Local File": "ས་གནས་ཀྱི་ཡིག་ཆ་ནས་ནང་འདྲེན།",
"TOC": "དཀར་ཆག",
"Table of Contents": "དཀར་ཆག",
"Book uploaded: {{title}}": "དཔེ་དེབ་སྤྲད་ཟིན། {{title}}",
"Failed to upload book: {{title}}": "དཔེ་དེབ་སྤྲད་མ་ཐུབ། {{title}}",
"Book downloaded: {{title}}": "དཔེ་དེབ་ཕབ་ལེན་བྱས་ཟིན། {{title}}",
@@ -538,5 +539,7 @@
"Close": "ཁ་རྒྱག",
"Delete Book Options": "དེབ་བཏོན་གཏང་གི་གདམ་ཁ",
"ON": "འབྱུང་ཁུངས",
"OFF": "བརྗེ་བ"
"OFF": "བརྗེ་བ",
"Reading Progress": "ཀློག་པའི་ཡར་འཕེལ།",
"Page Margin": "ཤོག་གདོང་།"
}
@@ -116,6 +116,7 @@
"Book Details": "Buchdetails",
"From Local File": "Aus lokaler Datei",
"TOC": "Inhaltsverzeichnis",
"Table of Contents": "Inhaltsverzeichnis",
"Book uploaded: {{title}}": "Buch hochgeladen: {{title}}",
"Failed to upload book: {{title}}": "Fehler beim Hochladen des Buches: {{title}}",
"Book downloaded: {{title}}": "Buch heruntergeladen: {{title}}",
@@ -542,5 +543,7 @@
"Close": "Schließen",
"Delete Book Options": "Buch-Löschoptionen",
"ON": "EIN",
"OFF": "AUS"
"OFF": "AUS",
"Reading Progress": "Lesefortschritt",
"Page Margin": "Seitenrand"
}
@@ -117,6 +117,7 @@
"Book Details": "Λεπτομέρειες βιβλίου",
"From Local File": "Από τοπικό αρχείο",
"TOC": "Πίνακας περιεχομένων",
"Table of Contents": "Πίνακας περιεχομένων",
"Book uploaded: {{title}}": "Το βιβλίο με τίτλο {{title}} ανέβηκε",
"Failed to upload book: {{title}}": "Αποτυχία ανέβασμα βιβλίου: {{title}}",
"Book downloaded: {{title}}": "Το βιβλίο με τίτλο {{title}} κατέβηκε",
@@ -542,5 +543,7 @@
"Close": "Κλείσιμο",
"Delete Book Options": "Επιλογές διαγραφής βιβλίου",
"ON": "ΕΝΕΡΓΟΠΟΙΗΣΗ",
"OFF": "ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ"
"OFF": "ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ",
"Reading Progress": "Πρόοδος ανάγνωσης",
"Page Margin": "Περιθώριο σελίδας"
}
@@ -144,6 +144,7 @@
"Book Details": "Detalles del libro",
"From Local File": "Desde archivo local",
"TOC": "Índice",
"Table of Contents": "Índice",
"Book uploaded: {{title}}": "Libro subido: {{title}}",
"Failed to upload book: {{title}}": "Error al subir libro: {{title}}",
"Book downloaded: {{title}}": "Libro descargado: {{title}}",
@@ -546,5 +547,7 @@
"Close": "Cerrar",
"Delete Book Options": "Opciones de Eliminar Libro",
"ON": "ENCENDIDO",
"OFF": "APAGADO"
"OFF": "APAGADO",
"Reading Progress": "Progreso de Lectura",
"Page Margin": "Márgenes de Página"
}
@@ -117,6 +117,7 @@
"Book Details": "Détails du livre",
"From Local File": "Depuis un fichier local",
"TOC": "Table des matières",
"Table of Contents": "Table des matières",
"Book uploaded: {{title}}": "Livre téléchargé : {{title}}",
"Failed to upload book: {{title}}": "Échec du téléchargement du livre : {{title}}",
"Book downloaded: {{title}}": "Livre téléchargé : {{title}}",
@@ -546,5 +547,7 @@
"Close": "Fermer",
"Delete Book Options": "Options de suppression du livre",
"ON": "ACTIVER",
"OFF": "DÉSACTIVER"
"OFF": "DÉSACTIVER",
"Reading Progress": "Progression de lecture",
"Page Margin": "Marge de page"
}
@@ -117,6 +117,7 @@
"Book Details": "पुस्तक विवरण",
"From Local File": "स्थानीय फ़ाइल से",
"TOC": "सामग्री",
"Table of Contents": "सामग्री",
"Book uploaded: {{title}}": "पुस्तक अपलोड की गई: {{title}}",
"Failed to upload book: {{title}}": "पुस्तक अपलोड करने में विफल: {{title}}",
"Book downloaded: {{title}}": "पुस्तक डाउनलोड की गई: {{title}}",
@@ -542,5 +543,7 @@
"Close": "बंद करें",
"Delete Book Options": "पुस्तक हटाने के विकल्प",
"ON": "चालू",
"OFF": "बंद"
"OFF": "बंद",
"Reading Progress": "पढ़ने की प्रगति",
"Page Margin": "पृष्ठ का मार्जिन"
}
@@ -117,6 +117,7 @@
"Book Details": "Detail Buku",
"From Local File": "Dari file lokal",
"TOC": "Daftar Isi",
"Table of Contents": "Daftar Isi",
"Book uploaded: {{title}}": "Buku diunggah: {{title}}",
"Failed to upload book: {{title}}": "Gagal mengunggah buku: {{title}}",
"Book downloaded: {{title}}": "Buku diunduh: {{title}}",
@@ -538,5 +539,7 @@
"Close": "Tutup",
"Delete Book Options": "Opsi Hapus Buku",
"ON": "AKTIF",
"OFF": "TIDAK AKTIF"
"OFF": "TIDAK AKTIF",
"Reading Progress": "Progres Membaca",
"Page Margin": "Margin Halaman"
}
@@ -117,6 +117,7 @@
"Book Details": "Dettagli libro",
"From Local File": "Da file locale",
"TOC": "Sommario",
"Table of Contents": "Sommario",
"Book uploaded: {{title}}": "Libro caricato: {{title}}",
"Failed to upload book: {{title}}": "Caricamento libro non riuscito: {{title}}",
"Book downloaded: {{title}}": "Libro scaricato: {{title}}",
@@ -546,5 +547,7 @@
"Close": "Chiudi",
"Delete Book Options": "Opzioni di eliminazione libro",
"ON": "ATTIVO",
"OFF": "DISATTIVO"
"OFF": "DISATTIVO",
"Reading Progress": "Progresso di lettura",
"Page Margin": "Margine di pagina"
}
@@ -117,6 +117,7 @@
"Book Details": "書籍の詳細",
"From Local File": "ローカルファイルから",
"TOC": "目次",
"Table of Contents": "目次",
"Book uploaded: {{title}}": "書籍がアップロードされました:{{title}}",
"Failed to upload book: {{title}}": "書籍のアップロードに失敗しました:{{title}}",
"Book downloaded: {{title}}": "書籍がダウンロードされました:{{title}}",
@@ -538,5 +539,7 @@
"Close": "閉じる",
"Delete Book Options": "本の削除オプション",
"ON": "オン",
"OFF": "オフ"
"OFF": "オフ",
"Reading Progress": "読書進捗",
"Page Margin": "ページマージン"
}
@@ -117,6 +117,7 @@
"Book Details": "책 세부 정보",
"From Local File": "로컬 파일에서",
"TOC": "목차",
"Table of Contents": "목차",
"Book uploaded: {{title}}": "책 업로드됨: {{title}}",
"Failed to upload book: {{title}}": "책 업로드 실패: {{title}}",
"Book downloaded: {{title}}": "책 다운로드됨: {{title}}",
@@ -538,5 +539,7 @@
"Close": "닫기",
"Delete Book Options": "도서 삭제 옵션",
"ON": "켜기",
"OFF": "끄기"
"OFF": "끄기",
"Reading Progress": "읽기 진행 상황",
"Page Margin": "페이지 여백"
}
@@ -210,6 +210,7 @@
"Match Whole Words": "Hele woorden",
"Match Diacritics": "Diakritische tekens",
"TOC": "Inhoudsopgave",
"Table of Contents": "Inhoudsopgave",
"Sidebar": "Zijbalk",
"TTS not supported for PDF": "TTS niet ondersteund voor PDF",
"No Timeout": "Geen time-out",
@@ -542,5 +543,7 @@
"Close": "Sluiten",
"Delete Book Options": "Boekverwijderopties",
"ON": "AAN",
"OFF": "UIT"
"OFF": "UIT",
"Reading Progress": "Leesvoortgang",
"Page Margin": "Pagina Marges"
}
@@ -117,6 +117,7 @@
"Book Details": "Szczegóły książki",
"From Local File": "Z pliku lokalnego",
"TOC": "Spis treści",
"Table of Contents": "Spis treści",
"Book uploaded: {{title}}": "Książka przesłana: {{title}}",
"Failed to upload book: {{title}}": "Nie udało się przesłać książki: {{title}}",
"Book downloaded: {{title}}": "Książka pobrana: {{title}}",
@@ -550,5 +551,7 @@
"Close": "Zamknij",
"Delete Book Options": "Opcje usuwania książki",
"ON": "WŁĄCZONE",
"OFF": "WYŁĄCZONE"
"OFF": "WYŁĄCZONE",
"Reading Progress": "Postęp czytania",
"Page Margin": "Margines strony"
}
@@ -117,6 +117,7 @@
"Book Details": "Detalhes do Livro",
"From Local File": "Do arquivo local",
"TOC": "Índice",
"Table of Contents": "Índice",
"Book uploaded: {{title}}": "Livro enviado: {{title}}",
"Failed to upload book: {{title}}": "Falha ao enviar livro: {{title}}",
"Book downloaded: {{title}}": "Livro baixado: {{title}}",
@@ -546,5 +547,7 @@
"Close": "Fechar",
"Delete Book Options": "Opções de Exclusão de Livro",
"ON": "LIGADO",
"OFF": "DESLIGADO"
"OFF": "DESLIGADO",
"Reading Progress": "Progresso de Leitura",
"Page Margin": "Margem da Página"
}
@@ -117,6 +117,7 @@
"Book Details": "Детали книги",
"From Local File": "Из локального файла",
"TOC": "Содержание",
"Table of Contents": "Содержание",
"Book uploaded: {{title}}": "Книга загружена: {{title}}",
"Failed to upload book: {{title}}": "Не удалось загрузить книгу: {{title}}",
"Book downloaded: {{title}}": "Книга скачана: {{title}}",
@@ -550,5 +551,7 @@
"Close": "Закрыть",
"Delete Book Options": "Параметры удаления книги",
"ON": "ВКЛ",
"OFF": "ВЫКЛ"
"OFF": "ВЫКЛ",
"Reading Progress": "Прогресс чтения",
"Page Margin": "Поля страницы"
}
@@ -323,6 +323,7 @@
"Match Whole Words": "සම්පූර්ණ වචන ගැළපීම",
"Match Diacritics": "ධ්වනි ලකුණු ගැළපීම",
"TOC": "සූචිය",
"Table of Contents": "සූචිය",
"Sidebar": "පැති තීරුව",
"Disable Translation": "පරිවර්තනය අබල කරන්න",
"Translation Not Available": "පරිවර්තනය ලබා ගත නොහැක",
@@ -542,5 +543,7 @@
"Close": "වසන්න",
"Delete Book Options": "පොත මකන විකල්ප",
"ON": "ඇක්ටිව්",
"OFF": "ඩිසැක්ටිව්"
"OFF": "ඩිසැක්ටිව්",
"Reading Progress": "කියවීමේ ප්‍රගතිය",
"Page Margin": "පිටු මාර්ජින්"
}
@@ -323,6 +323,7 @@
"Match Whole Words": "முழு வார்த்தைகள் பொருத்தம்",
"Match Diacritics": "உச்சரிப்பு குறிகள் பொருத்தம்",
"TOC": "உள்ளடக்கம்",
"Table of Contents": "உள்ளடக்கம்",
"Sidebar": "பக்கப்பட்டி",
"Disable Translation": "மொழிபெயர்ப்பை முடக்கவும்",
"Translation Not Available": "மொழிபெயர்ப்பு கிடைக்கவில்லை",
@@ -542,5 +543,7 @@
"Close": "மூடு",
"Delete Book Options": "புத்தகத்தை நீக்கும் விருப்பங்கள்",
"ON": "செயல்படுத்தவும்",
"OFF": "நிறுத்தவும்"
"OFF": "நிறுத்தவும்",
"Reading Progress": "வாசிப்பு முன்னேற்றம்",
"Page Margin": "பக்கம் மாறின"
}
@@ -273,6 +273,7 @@
"Match Whole Words": "ตรงทั้งคำ",
"Match Diacritics": "ตรงตามเครื่องหมายวรรณยุกต์",
"TOC": "สารบัญ",
"Table of Contents": "สารบัญ",
"Sidebar": "แถบข้าง",
"Disable Translation": "ปิดการแปล",
"Translation Not Available": "ไม่มีการแปล",
@@ -538,5 +539,7 @@
"Close": "ปิด",
"Delete Book Options": "ตัวเลือกการลบหนังสือ",
"ON": "เปิด",
"OFF": "ปิด"
"OFF": "ปิด",
"Reading Progress": "ความคืบหน้าในการอ่าน",
"Page Margin": "ขอบหน้า"
}
@@ -117,6 +117,7 @@
"Book Details": "Kitap Detayları",
"From Local File": "Yerel dosyadan",
"TOC": "İçindekiler",
"Table of Contents": "İçindekiler",
"Book uploaded: {{title}}": "Kitap yüklendi: {{title}}",
"Failed to upload book: {{title}}": "Kitap yüklenemedi: {{title}}",
"Book downloaded: {{title}}": "Kitap indirildi: {{title}}",
@@ -542,5 +543,7 @@
"Close": "Kapat",
"Delete Book Options": "Kitap Silme Seçenekleri",
"ON": "AÇIK",
"OFF": "KAPALI"
"OFF": "KAPALI",
"Reading Progress": "Okuma İlerlemesi",
"Page Margin": "Sayfa Kenar Boşluğu"
}
@@ -117,6 +117,7 @@
"Book Details": "Деталі книги",
"From Local File": "З локального файлу",
"TOC": "Зміст",
"Table of Contents": "Зміст",
"Book uploaded: {{title}}": "Книгу завантажено: {{title}}",
"Failed to upload book: {{title}}": "Не вдалося завантажити книгу: {{title}}",
"Book downloaded: {{title}}": "Книгу завантажено: {{title}}",
@@ -550,5 +551,7 @@
"Close": "Закрити",
"Delete Book Options": "Параметри видалення книги",
"ON": "УВІМКНЕННЯ",
"OFF": "ВИМКНЕННЯ"
"OFF": "ВИМКНЕННЯ",
"Reading Progress": "Проґрес читання",
"Page Margin": "Поля сторінки"
}
@@ -117,6 +117,7 @@
"Book Details": "Chi tiết sách",
"From Local File": "Từ tệp địa phương",
"TOC": "Mục lục",
"Table of Contents": "Mục lục",
"Book uploaded: {{title}}": "Sách đã tải lên: {{title}}",
"Failed to upload book: {{title}}": "Không thể tải lên sách: {{title}}",
"Book downloaded: {{title}}": "Sách đã tải về: {{title}}",
@@ -538,5 +539,7 @@
"Close": "Đóng",
"Delete Book Options": "Tùy chọn xóa sách",
"ON": "BẬT",
"OFF": "TẮT"
"OFF": "TẮT",
"Reading Progress": "Tiến độ đọc",
"Page Margin": "Lề trang"
}
@@ -117,6 +117,7 @@
"Book Details": "书籍详情",
"From Local File": "从本地文件导入",
"TOC": "目录",
"Table of Contents": "目录",
"Book uploaded: {{title}}": "书籍已上传:{{title}}",
"Failed to upload book: {{title}}": "上传书籍失败:{{title}}",
"Book downloaded: {{title}}": "书籍已下载:{{title}}",
@@ -538,5 +539,7 @@
"Close": "关闭",
"Delete Book Options": "删除书籍选项",
"ON": "开启",
"OFF": "关闭"
"OFF": "关闭",
"Reading Progress": "阅读进度",
"Page Margin": "页面边距"
}
@@ -117,6 +117,7 @@
"Book Details": "書籍詳情",
"From Local File": "從本地文件導入",
"TOC": "目錄",
"Table of Contents": "目錄",
"Book uploaded: {{title}}": "書籍已上傳:{{title}}",
"Failed to upload book: {{title}}": "書籍上傳失敗:{{title}}",
"Book downloaded: {{title}}": "書籍已下載:{{title}}",
@@ -538,5 +539,7 @@
"Close": "關閉",
"Delete Book Options": "刪除書籍選項",
"ON": "開啟",
"OFF": "關閉"
"OFF": "關閉",
"Reading Progress": "閱讀進度",
"Page Margin": "頁邊距"
}
@@ -327,7 +327,7 @@ const Bookshelf: React.FC<BookshelfProps> = ({
viewMode === 'grid' && 'sm:grid-cols-4 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-12',
viewMode === 'list' && 'flex flex-col',
)}
role='list'
role='main'
aria-label={_('Bookshelf')}
>
{filteredBookshelfItems.map((item) => (
@@ -312,6 +312,17 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
[isSelectMode, handleSelectItem, handleOpenItem, handleContextMenu],
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleOpenItem();
}
if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {
e.preventDefault();
handleContextMenu();
}
};
return (
<div className={clsx(mode === 'list' && 'sm:hover:bg-base-300/50 px-4 sm:px-6')}>
<div
@@ -319,14 +330,17 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
'group',
mode === 'grid' && 'sm:hover:bg-base-300/50 flex h-full flex-col px-0 py-4 sm:px-4',
mode === 'list' && 'border-base-300 flex flex-col border-b py-2',
'focus-inset-2',
appService?.isMobileApp && 'no-context-menu',
pressing && mode === 'grid' ? 'scale-95' : 'scale-100',
)}
role='group'
role='button'
tabIndex={0}
aria-label={'format' in item ? item.title : item.name}
style={{
transition: 'transform 0.2s',
}}
onKeyDown={handleKeyDown}
{...handlers}
>
<div className='flex-grow'>
@@ -627,7 +627,6 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
return (
<div
ref={pageRef}
role='main'
aria-label='Your Library'
className={clsx(
'library-page bg-base-200 text-base-content flex select-none flex-col overflow-hidden',
@@ -222,6 +222,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
>
<div className='flex w-full items-center justify-between gap-x-6'>
<Slider
label={_('Reading Progress')}
heightPx={sliderHeight}
bubbleLabel={`${Math.round(progressFraction * 100)}%`}
initialValue={progressValid ? progressFraction * 100 : 0}
@@ -275,6 +276,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
}}
>
<Slider
label={_('Font Size')}
initialValue={viewSettings?.defaultFontSize ?? 16}
bubbleLabel={`${viewSettings?.defaultFontSize ?? 16}`}
minLabel='A'
@@ -287,6 +289,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
/>
<div className='flex w-full items-center justify-between gap-x-6'>
<Slider
label={_('Page Margin')}
initialValue={getMarginProgressValue(
viewSettings?.marginTopPx ?? 44,
viewSettings?.gapPercent ?? 5,
@@ -298,6 +301,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
onChange={handleMarginChange}
/>
<Slider
label={_('Line Spacing')}
initialValue={(viewSettings?.lineHeight ?? 1.6) * 10}
bubbleElement={<RxLineHeight size={marginIconSize} />}
minLabel={_('Small')}
@@ -317,15 +321,22 @@ const FooterBar: React.FC<FooterBarProps> = ({
}}
>
<Button
tooltip={_('Table of Contents')}
icon={<TOCIcon size={tocIconSize} className='' />}
onClick={() => handleSetActionTab('toc')}
/>
<Button icon={<NoteIcon className='' />} onClick={() => handleSetActionTab('note')} />
<Button
tooltip={_('Notes')}
icon={<NoteIcon className='' />}
onClick={() => handleSetActionTab('note')}
/>
<Button
tooltip={_('Reading Progress')}
icon={<SliderIcon className={clsx(actionTab === 'progress' && 'text-blue-500')} />}
onClick={() => handleSetActionTab('progress')}
/>
<Button
tooltip={_('Font & Layout')}
icon={
<FontIcon
size={fontIconSize}
@@ -335,6 +346,7 @@ const FooterBar: React.FC<FooterBarProps> = ({
onClick={() => handleSetActionTab('font')}
/>
<Button
tooltip={_('Speak')}
icon={<TTSIcon className={ttsEnabled ? 'text-blue-500' : ''} />}
onClick={() => handleSetActionTab('tts')}
/>
@@ -184,7 +184,8 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
</div>
{availableFamilies.map((family) => (
<button
<div
role='none'
key={family.name}
className={clsx(
'card h-12 border shadow-sm',
@@ -215,7 +216,7 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
</button>
)}
</div>
</button>
</div>
))}
</div>
@@ -48,7 +48,7 @@ const PlanCard: React.FC<PlanCardProps> = ({
</div>
</div>
<button className='mb-6 space-y-3' onClick={() => onSelectPlan(index)}>
<div role='none' className='mb-6 space-y-3' onClick={() => onSelectPlan(index)}>
{plan.features.map((feature, featureIndex) => (
<div key={featureIndex} className='flex flex-col'>
<div className='flex items-center gap-2'>
@@ -62,9 +62,13 @@ const PlanCard: React.FC<PlanCardProps> = ({
)}
</div>
))}
</button>
</div>
<button className='mb-6 rounded-lg bg-white/50 p-4' onClick={() => onSelectPlan(index)}>
<div
role='none'
className='mb-6 rounded-lg bg-white/50 p-4'
onClick={() => onSelectPlan(index)}
>
<h5 className='mb-3 font-semibold'>{_('Plan Limits')}</h5>
<div className='space-y-2'>
{Object.entries(plan.limits).map(([key, value]) => (
@@ -74,7 +78,7 @@ const PlanCard: React.FC<PlanCardProps> = ({
</div>
))}
</div>
</button>
</div>
<PlanActionButton
plan={plan}
+45 -38
View File
@@ -62,48 +62,50 @@ const Dropdown: React.FC<DropdownProps> = ({
}) => {
const [isOpen, setIsOpen] = useState(false);
const [isFocused, setIsFocused] = useState(false);
const [isPointerDown, setIsPointerDown] = useState(false);
const lastInteractionWasTapOrClick = useRef(false);
const containerRef = useRef<HTMLDivElement>(null);
const setIsDropdownOpen = (open: boolean) => {
setIsOpen(open);
onToggle?.(open);
};
const handleTouchOrClick = () => {
lastInteractionWasTapOrClick.current = true;
setTimeout(() => (lastInteractionWasTapOrClick.current = false), 100);
};
const handleFocus = () => {
setIsFocused(true);
// skip touch and pointer triggered focus, this is only for keyboard and aria navigation
if (!lastInteractionWasTapOrClick.current) {
setIsDropdownOpen(true);
}
};
const toggleDropdown = () => {
console.error('Toggling dropdown, current state:', isOpen);
const newIsOpen = !isOpen;
setIsDropdownOpen(newIsOpen);
setIsFocused(!isOpen);
setIsDropdownOpen(!isOpen);
};
const handleBlur = (e: React.FocusEvent) => {
if (!containerRef.current) return;
if (!containerRef.current.contains(e.relatedTarget as Node)) {
setIsFocused(false);
setIsDropdownOpen(false);
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
if (!isOpen) setIsDropdownOpen(true);
e.stopPropagation();
} else if (e.key === 'Escape' && isOpen) {
} else if (e.key === 'Escape') {
setIsDropdownOpen(false);
e.stopPropagation();
}
};
const handleFocus = () => {
if (!isOpen && !isPointerDown) {
setIsDropdownOpen(true);
}
setIsFocused(true);
};
const handleBlur = (e: React.FocusEvent) => {
if (!containerRef.current) return;
const relatedTarget = e.relatedTarget;
if (relatedTarget && !containerRef.current.contains(relatedTarget)) {
console.log('Dropdown lost focus, closing menu');
setIsFocused(false);
setIsDropdownOpen(false);
}
};
const setIsDropdownOpen = (isOpen: boolean) => {
setIsOpen(isOpen);
onToggle?.(isOpen);
};
const childrenWithToggle = isValidElement(children)
? React.cloneElement(children, {
...(typeof children.type !== 'string' && {
@@ -120,24 +122,29 @@ const Dropdown: React.FC<DropdownProps> = ({
<div
ref={containerRef}
role='menu'
tabIndex={0}
aria-label={label}
tabIndex={-1}
title={label}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
onPointerDown={() => setIsPointerDown(true)}
onPointerUp={() => setIsPointerDown(false)}
className={clsx('dropdown flex flex-col', className)}
>
<div
role='none'
className={clsx('dropdown-toggle', buttonClassName, isFocused && 'bg-base-300/50')}
<button
aria-haspopup='menu'
aria-expanded={isOpen}
aria-label={label}
className={clsx(
'dropdown-toggle',
isFocused && isOpen && 'bg-base-300/50',
buttonClassName,
)}
onTouchStart={handleTouchOrClick}
onPointerDown={handleTouchOrClick}
onFocus={handleFocus}
onClick={toggleDropdown}
>
{toggleButton}
</div>
<div role='none' className={clsx('flex items-center justify-center', !isOpen && 'hidden')}>
</button>
<div role='none' className={clsx('flex items-center justify-center')}>
{isOpen && childrenWithToggle}
</div>
</div>
@@ -12,7 +12,7 @@ const LegalLinks = () => {
: 'https://readest.com/terms-of-service';
return (
<div className='my-2 flex flex-wrap justify-center gap-4 text-xs'>
<div className='my-2 flex flex-wrap justify-center gap-4 text-sm sm:text-xs'>
<Link href={termsUrl} className='text-blue-500 underline hover:text-blue-600'>
{_('Terms of Service')}
</Link>
@@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from 'react';
interface SliderProps {
label: string;
min?: number;
max?: number;
step?: number;
@@ -18,6 +19,7 @@ interface SliderProps {
}
const Slider: React.FC<SliderProps> = ({
label,
min = 0,
max = 100,
step = 1,
@@ -65,6 +67,7 @@ const Slider: React.FC<SliderProps> = ({
return (
<div
ref={sliderRef}
aria-label={label}
className={`slider bg-base-200 mx-auto w-full rounded-xl ${className}`}
dir={isRtl ? 'rtl' : undefined}
>
@@ -111,6 +114,12 @@ const Slider: React.FC<SliderProps> = ({
value={value}
className='absolute inset-0 h-full w-full cursor-pointer opacity-0'
onChange={handleChange}
aria-label={label}
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
aria-valuetext={`${value}`}
aria-orientation='horizontal'
/>
</div>
</div>
@@ -5,7 +5,7 @@ import Link from './Link';
const SupportLinks = () => {
const _ = useTranslation();
const iconSize = useResponsiveSize(22);
const iconSize = useResponsiveSize(24);
return (
<div className='my-2 flex flex-col items-center gap-2'>
+8
View File
@@ -365,3 +365,11 @@ foliate-view {
rgba(0,0,0,0.5) 100%
);
}
.focus-inset-2 {
@apply focus:outline-none;
}
.focus-inset-2:focus {
box-shadow: inset 0 0 0 2px transparent, inset 0 0 0 3px #6396e7;
}