Skip to content

Commit 9e79b03

Browse files
authored
Merge pull request #192 from anantham/claude/mobile-a11y
Accessibility on phones and iPads: contrast, touch targets, settings layout, screen-reader labels
2 parents 79443eb + 6923ca0 commit 9e79b03

28 files changed

Lines changed: 725 additions & 161 deletions

‎components/InputBar.tsx‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ const InputBar: React.FC = () => {
158158
const isAnyLoading = isLoading || isImporting;
159159

160160
const tabClass = (active: boolean) =>
161-
`px-4 py-2 text-sm font-medium rounded-t-lg transition ${
161+
`px-4 py-2 pointer-coarse:min-h-11 text-sm font-medium rounded-t-lg transition ${
162162
active
163163
? 'bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 border-b-2 border-blue-500'
164-
: 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
164+
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white'
165165
}`;
166166

167167
return (
@@ -170,6 +170,7 @@ const InputBar: React.FC = () => {
170170
<div className="flex border-b border-gray-200 dark:border-gray-700 px-4 pt-3 gap-1">
171171
<button
172172
type="button"
173+
aria-pressed={mode === 'url'}
173174
className={tabClass(mode === 'url')}
174175
onClick={() => setMode('url')}
175176
disabled={isAnyLoading}
@@ -178,6 +179,7 @@ const InputBar: React.FC = () => {
178179
</button>
179180
<button
180181
type="button"
182+
aria-pressed={mode === 'paste'}
181183
className={tabClass(mode === 'paste')}
182184
onClick={() => setMode('paste')}
183185
disabled={isAnyLoading}
@@ -214,15 +216,15 @@ const InputBar: React.FC = () => {
214216
type="button"
215217
onClick={() => fileInputRef.current?.click()}
216218
disabled={isAnyLoading}
217-
className="px-4 py-2 bg-gray-600 text-white font-semibold rounded-md shadow-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 dark:focus:ring-offset-gray-800 disabled:bg-gray-400 dark:disabled:bg-gray-800 disabled:cursor-not-allowed transition duration-300 ease-in-out"
219+
className="px-4 py-2 pointer-coarse:min-h-11 bg-gray-600 text-white font-semibold rounded-md shadow-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 dark:focus:ring-offset-gray-800 disabled:bg-gray-400 dark:disabled:bg-gray-800 disabled:cursor-not-allowed transition duration-300 ease-in-out"
218220
title="Import session from a JSON file on your computer"
219221
>
220222
📁 Import
221223
</button>
222224
<button
223225
type="submit"
224226
disabled={isAnyLoading || !url.trim()}
225-
className="px-4 py-2 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition duration-300 ease-in-out"
227+
className="px-4 py-2 pointer-coarse:min-h-11 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition duration-300 ease-in-out"
226228
title={url.trim() ? 'Fetch chapter or session from URL' : 'Enter a URL first'}
227229
>
228230
{isImporting ? 'Importing...' : isLoading ? 'Fetching...' : '🔗 Fetch'}
@@ -277,7 +279,7 @@ const InputBar: React.FC = () => {
277279
href={site.homeUrl}
278280
target="_blank"
279281
rel="noopener noreferrer"
280-
className="text-blue-500 hover:underline"
282+
className="text-blue-700 dark:text-blue-400 underline underline-offset-2"
281283
title={`Visit ${site.name} - ${category}`}
282284
onClick={(e) => {
283285
e.preventDefault();
@@ -290,7 +292,7 @@ const InputBar: React.FC = () => {
290292
</React.Fragment>
291293
))}
292294
{' '}
293-
<span className="text-gray-500">({category})</span>
295+
<span className="text-gray-600 dark:text-gray-400">({category})</span>
294296
{categoryIndex < categories.length - 1 && '; '}
295297
</React.Fragment>
296298
))}
@@ -299,7 +301,7 @@ const InputBar: React.FC = () => {
299301
href="https://t.me/webnovels"
300302
target="_blank"
301303
rel="noopener noreferrer"
302-
className="text-blue-500 hover:underline"
304+
className="text-blue-700 dark:text-blue-400 underline underline-offset-2"
303305
>
304306
request for us to add support for your fav website here!
305307
</a>
@@ -370,7 +372,7 @@ const InputBar: React.FC = () => {
370372
type="button"
371373
onClick={handlePasteSubmit}
372374
disabled={isAnyLoading || !pasteContent.trim()}
373-
className="px-4 py-2 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition duration-300 ease-in-out"
375+
className="px-4 py-2 pointer-coarse:min-h-11 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition duration-300 ease-in-out"
374376
>
375377
{isLoading ? 'Importing...' : 'Import Text'}
376378
</button>

‎components/SessionInfo.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ const SessionInfo: React.FC = () => {
254254
};
255255

256256
return (
257-
<div className="w-full max-w-4xl mx-auto -mt-2 mb-6 p-3 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-b-xl shadow-lg flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 border-t border-gray-200 dark:border-gray-700">
257+
<div className="w-full max-w-4xl mx-auto -mt-2 mb-6 p-3 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-b-xl shadow-lg flex flex-row justify-between items-start sm:items-center gap-3 sm:gap-4 border-t border-gray-200 dark:border-gray-700">
258258
<div className="flex-grow w-full sm:w-auto flex flex-col gap-2 min-w-0">
259259
{/* Chapter dropdown row */}
260260
<div className="flex items-center gap-2">
@@ -303,7 +303,7 @@ const SessionInfo: React.FC = () => {
303303
<button
304304
onClick={() => setShowExportModal(true)}
305305
disabled={sessionIsEmpty || isExporting}
306-
className="px-3 py-1.5 bg-green-600 text-white text-sm font-semibold rounded-md shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 dark:focus:ring-offset-gray-800 disabled:bg-gray-400 dark:disabled:bg-gray-600 disabled:cursor-not-allowed transition duration-300 ease-in-out"
306+
className="px-3 py-1.5 pointer-coarse:min-h-11 bg-green-700 text-white text-sm font-semibold rounded-md shadow-sm hover:bg-green-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 dark:focus:ring-offset-gray-800 disabled:bg-gray-400 dark:disabled:bg-gray-600 disabled:cursor-not-allowed transition duration-300 ease-in-out"
307307
>
308308
{isExporting ? 'Exporting...' : 'Export Book'}
309309
</button>
@@ -312,11 +312,12 @@ const SessionInfo: React.FC = () => {
312312
</div>
313313

314314
{/* Settings button */}
315-
<div className="flex items-center gap-2 flex-shrink-0 w-full sm:w-auto justify-center sm:justify-end">
315+
<div className="flex items-center gap-2 flex-shrink-0">
316316
<button
317317
onClick={() => setShowSettingsModal(true)}
318-
className="p-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-full shadow-sm hover:bg-gray-300 dark:hover:bg-gray-600 transition"
318+
className="p-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11 inline-flex items-center justify-center bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-full shadow-sm hover:bg-gray-300 dark:hover:bg-gray-600 transition"
319319
title="Settings"
320+
aria-label="Settings"
320321
>
321322
<SettingsIcon className="w-5 h-5" />
322323
</button>

‎components/SettingsModal.tsx‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { SettingsModalProvider, ParameterSupportState } from './settings/Setting
1818
import DisplayPanel from './settings/DisplayPanel';
1919
import SessionActions from './settings/SessionActions';
2020
import { useNovelMetadata } from '../hooks/useNovelMetadata';
21+
import { useModalDialog } from '../hooks/useModalDialog';
2122

2223

2324
interface SettingsModalProps {
@@ -169,6 +170,9 @@ const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose }) => {
169170
onClose();
170171
};
171172

173+
const dialogRef = useRef<HTMLDivElement>(null);
174+
useModalDialog(dialogRef, isOpen, onClose);
175+
172176
if (!isOpen) return null;
173177

174178
const handleCancel = () => {
@@ -184,9 +188,17 @@ const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose }) => {
184188

185189
return (
186190
<div className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50 p-2 sm:p-4" onClick={handleCancel}>
187-
<div className="bg-white dark:bg-gray-800 rounded-xl sm:rounded-2xl shadow-2xl w-full max-w-md sm:max-w-2xl lg:max-w-3xl max-h-[95vh] sm:max-h-[90vh] flex flex-col" onClick={(e) => e.stopPropagation()}>
191+
<div
192+
ref={dialogRef}
193+
role="dialog"
194+
aria-modal="true"
195+
aria-labelledby="settings-modal-title"
196+
tabIndex={-1}
197+
className="bg-white dark:bg-gray-800 rounded-xl sm:rounded-2xl shadow-2xl w-full max-w-md sm:max-w-2xl lg:max-w-3xl max-h-[95dvh] sm:max-h-[90dvh] flex flex-col"
198+
onClick={(e) => e.stopPropagation()}
199+
>
188200
<header className="px-6 sm:px-8 py-4 border-b border-gray-200 dark:border-gray-700">
189-
<h2 className="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">
201+
<h2 id="settings-modal-title" className="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">
190202
Settings
191203
</h2>
192204
<p className="text-gray-600 dark:text-gray-400 mt-1">
@@ -195,7 +207,7 @@ const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose }) => {
195207
</header>
196208

197209
<SettingsModalProvider value={modalContextValue}>
198-
<div className="flex flex-1 overflow-hidden">
210+
<div className="flex flex-col sm:flex-row flex-1 min-h-0 overflow-hidden">
199211
<SettingsSidebar
200212
sections={sidebarSections}
201213
activeItem={activePanel}

‎components/chapter/ChapterHeader.tsx‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ interface ChapterHeaderProps {
2727
const languageButtonClasses = (
2828
isActive: boolean
2929
) =>
30-
`px-4 py-1 text-sm font-semibold rounded-full transition-colors ${
31-
isActive ? 'bg-white dark:bg-gray-900 text-gray-800 dark:text-white shadow' : 'text-gray-600 dark:text-gray-400'
30+
`px-4 py-1 pointer-coarse:min-h-11 text-sm font-semibold rounded-full transition-colors ${
31+
isActive ? 'bg-white dark:bg-gray-900 text-gray-800 dark:text-white shadow' : 'text-gray-700 dark:text-gray-300'
3232
}`;
3333

3434
const mobileLanguageButtonClasses = (
3535
isActive: boolean
3636
) =>
37-
`px-3 py-1 text-xs font-semibold rounded-full transition-colors ${
38-
isActive ? 'bg-white dark:bg-gray-900 text-gray-800 dark:text-white shadow' : 'text-gray-600 dark:text-gray-400'
37+
`px-3 py-1 pointer-coarse:min-h-11 text-xs font-semibold rounded-full transition-colors ${
38+
isActive ? 'bg-white dark:bg-gray-900 text-gray-800 dark:text-white shadow' : 'text-gray-700 dark:text-gray-300'
3939
}`;
4040

4141
const ChapterHeader: React.FC<ChapterHeaderProps> = ({
@@ -104,7 +104,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
104104
<button
105105
onClick={onNavigatePrev}
106106
disabled={prevDisabled}
107-
className="px-5 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition"
107+
className="px-5 py-2 pointer-coarse:min-h-11 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition"
108108
>
109109
&larr; Previous
110110
</button>
@@ -113,7 +113,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
113113
{onOpenLibrary && (
114114
<button
115115
onClick={onOpenLibrary}
116-
className="p-2 bg-amber-100 dark:bg-amber-900/30 text-amber-900 dark:text-amber-100 rounded-md hover:bg-amber-200 dark:hover:bg-amber-900/50 transition"
116+
className="p-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11 inline-flex items-center justify-center bg-amber-100 dark:bg-amber-900/30 text-amber-900 dark:text-amber-100 rounded-md hover:bg-amber-200 dark:hover:bg-amber-900/50 transition"
117117
title="Return to the novel library"
118118
aria-label="Return to library (home)"
119119
>
@@ -127,7 +127,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
127127
href={sourceUrl}
128128
target="_blank"
129129
rel="noopener noreferrer"
130-
className="text-blue-500 hover:underline font-semibold text-sm"
130+
className="text-blue-700 dark:text-blue-400 hover:underline font-semibold text-sm inline-flex items-center min-h-11 px-1"
131131
title="View original source"
132132
>
133133
Source
@@ -137,7 +137,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
137137
<a
138138
href={suttaStudioUrl}
139139
onClick={makeSpaClickHandler(suttaStudioUrl)}
140-
className="w-9 h-9 rounded-full flex items-center justify-center border border-emerald-500/40 text-emerald-500 hover:bg-emerald-500/10 transition"
140+
className="w-9 h-9 pointer-coarse:w-11 pointer-coarse:h-11 rounded-full flex items-center justify-center border border-emerald-500/40 text-emerald-500 hover:bg-emerald-500/10 transition"
141141
title="Open Sutta Studio"
142142
aria-label="Open Sutta Studio"
143143
>
@@ -154,7 +154,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
154154
<button
155155
onClick={onRetranslateClick}
156156
disabled={retranslateDisabled}
157-
className={`p-2 rounded-full border transition-all duration-200 ${
157+
className={`p-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11 inline-flex items-center justify-center rounded-full border transition-all duration-200 ${
158158
isRetranslationActive
159159
? 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-700 hover:bg-red-100 dark:hover:bg-red-900/40'
160160
: retranslateDisabled
@@ -172,7 +172,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
172172
<button
173173
onClick={onNavigateNext}
174174
disabled={nextDisabled}
175-
className="px-5 py-2 bg-blue-600 text-white font-semibold rounded-md hover:bg-blue-700 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition"
175+
className="px-5 py-2 pointer-coarse:min-h-11 bg-blue-600 text-white font-semibold rounded-md hover:bg-blue-700 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition"
176176
>
177177
Next &rarr;
178178
</button>
@@ -184,7 +184,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
184184
{onOpenLibrary && (
185185
<button
186186
onClick={onOpenLibrary}
187-
className="p-2 bg-amber-100 dark:bg-amber-900/30 text-amber-900 dark:text-amber-100 rounded-md hover:bg-amber-200 dark:hover:bg-amber-900/50 transition"
187+
className="p-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11 inline-flex items-center justify-center bg-amber-100 dark:bg-amber-900/30 text-amber-900 dark:text-amber-100 rounded-md hover:bg-amber-200 dark:hover:bg-amber-900/50 transition"
188188
title="Return to the novel library"
189189
aria-label="Return to library (home)"
190190
>
@@ -196,14 +196,14 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
196196
<button
197197
onClick={onNavigatePrev}
198198
disabled={prevDisabled}
199-
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-medium rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm"
199+
className="px-4 py-2 pointer-coarse:min-h-11 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-medium rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition text-sm"
200200
>
201201
&larr; Prev
202202
</button>
203203
<button
204204
onClick={onNavigateNext}
205205
disabled={nextDisabled}
206-
className="px-4 py-2 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition text-sm"
206+
className="px-4 py-2 pointer-coarse:min-h-11 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition text-sm"
207207
>
208208
Next &rarr;
209209
</button>
@@ -215,7 +215,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
215215
<a
216216
href={suttaStudioUrl}
217217
onClick={makeSpaClickHandler(suttaStudioUrl)}
218-
className="w-8 h-8 rounded-full flex items-center justify-center border border-emerald-500/40 text-emerald-500 hover:bg-emerald-500/10 transition"
218+
className="w-8 h-8 pointer-coarse:w-11 pointer-coarse:h-11 rounded-full flex items-center justify-center border border-emerald-500/40 text-emerald-500 hover:bg-emerald-500/10 transition"
219219
title="Open Sutta Studio"
220220
aria-label="Open Sutta Studio"
221221
>
@@ -231,7 +231,7 @@ const ChapterHeader: React.FC<ChapterHeaderProps> = ({
231231
<button
232232
onClick={onRetranslateClick}
233233
disabled={retranslateDisabled}
234-
className={`p-2 rounded-full border transition-all duration-200 ${
234+
className={`p-2 pointer-coarse:min-h-11 pointer-coarse:min-w-11 inline-flex items-center justify-center rounded-full border transition-all duration-200 ${
235235
isRetranslationActive
236236
? 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-700 hover:bg-red-100 dark:hover:bg-red-900/40'
237237
: retranslateDisabled

‎components/chapter/FooterNavigation.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ const FooterNavigation: React.FC<FooterNavigationProps> = ({
1919
<button
2020
onClick={onNavigatePrev}
2121
disabled={!prevUrl || isLoading}
22-
className="px-5 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition"
22+
className="px-5 py-2 pointer-coarse:min-h-11 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 font-semibold rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition"
2323
>
2424
&larr; Previous
2525
</button>
2626
<button
2727
onClick={onNavigateNext}
2828
disabled={!nextUrl || isLoading}
29-
className="px-5 py-2 bg-blue-600 text-white font-semibold rounded-md hover:bg-blue-700 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition"
29+
className="px-5 py-2 pointer-coarse:min-h-11 bg-blue-600 text-white font-semibold rounded-md hover:bg-blue-700 disabled:bg-blue-400 dark:disabled:bg-blue-800 disabled:cursor-not-allowed transition"
3030
>
3131
Next &rarr;
3232
</button>

0 commit comments

Comments
 (0)