@@ -13,6 +13,7 @@ import {
13
13
SquaresPlusIcon ,
14
14
} from '@heroicons/react/24/outline' ;
15
15
import { OpenInNewTab } from '../utils/common' ;
16
+ import { ConfirmModal , AlertModal } from './CustomModals' ;
16
17
17
18
type SettKey = keyof typeof CONFIG_DEFAULT ;
18
19
@@ -277,16 +278,36 @@ export default function SettingDialog({
277
278
} ) {
278
279
const { config, saveConfig } = useAppContext ( ) ;
279
280
const [ sectionIdx , setSectionIdx ] = useState ( 0 ) ;
281
+ const [ showResetConfirm , setShowResetConfirm ] = useState ( false ) ;
282
+ const [ alertState , setAlertState ] = useState ( {
283
+ isOpen : false ,
284
+ message : '' ,
285
+ } ) ;
280
286
281
287
// clone the config object to prevent direct mutation
282
288
const [ localConfig , setLocalConfig ] = useState < typeof CONFIG_DEFAULT > (
283
289
JSON . parse ( JSON . stringify ( config ) )
284
290
) ;
285
291
286
292
const resetConfig = ( ) => {
287
- if ( window . confirm ( 'Are you sure you want to reset all settings?' ) ) {
288
- setLocalConfig ( CONFIG_DEFAULT ) ;
289
- }
293
+ setShowResetConfirm ( true ) ;
294
+ } ;
295
+
296
+ const handleResetConfirm = ( ) => {
297
+ setLocalConfig ( CONFIG_DEFAULT ) ;
298
+ setShowResetConfirm ( false ) ;
299
+ } ;
300
+
301
+ const handleResetCancel = ( ) => {
302
+ setShowResetConfirm ( false ) ;
303
+ } ;
304
+
305
+ const showAlert = ( message : string ) => {
306
+ setAlertState ( { isOpen : true , message } ) ;
307
+ } ;
308
+
309
+ const closeAlert = ( ) => {
310
+ setAlertState ( { isOpen : false , message : '' } ) ;
290
311
} ;
291
312
292
313
const handleSave = ( ) => {
@@ -302,22 +323,22 @@ export default function SettingDialog({
302
323
const mustBeNumeric = isNumeric ( CONFIG_DEFAULT [ key as SettKey ] ) ;
303
324
if ( mustBeString ) {
304
325
if ( ! isString ( value ) ) {
305
- alert ( `Value for ${ key } must be string` ) ;
326
+ showAlert ( `Value for ${ key } must be string` ) ;
306
327
return ;
307
328
}
308
329
} else if ( mustBeNumeric ) {
309
330
const trimmedValue = value . toString ( ) . trim ( ) ;
310
331
const numVal = Number ( trimmedValue ) ;
311
332
if ( isNaN ( numVal ) || ! isNumeric ( numVal ) || trimmedValue . length === 0 ) {
312
- alert ( `Value for ${ key } must be numeric` ) ;
333
+ showAlert ( `Value for ${ key } must be numeric` ) ;
313
334
return ;
314
335
}
315
336
// force conversion to number
316
337
// @ts -expect-error this is safe
317
338
newConfig [ key ] = numVal ;
318
339
} else if ( mustBeBoolean ) {
319
340
if ( ! isBoolean ( value ) ) {
320
- alert ( `Value for ${ key } must be boolean` ) ;
341
+ showAlert ( `Value for ${ key } must be boolean` ) ;
321
342
return ;
322
343
}
323
344
} else {
@@ -335,130 +356,143 @@ export default function SettingDialog({
335
356
} ;
336
357
337
358
return (
338
- < dialog
339
- className = { classNames ( { modal : true , 'modal-open' : show } ) }
340
- aria-label = "Settings dialog"
341
- >
342
- < div className = "modal-box w-11/12 max-w-3xl" >
343
- < h3 className = "text-lg font-bold mb-6" > Settings</ h3 >
344
- < div className = "flex flex-col md:flex-row h-[calc(90vh-12rem)]" >
345
- { /* Left panel, showing sections - Desktop version */ }
346
- < div
347
- className = "hidden md:flex flex-col items-stretch pr-4 mr-4 border-r-2 border-base-200"
348
- role = "complementary"
349
- aria-description = "Settings sections"
350
- tabIndex = { 0 }
351
- >
352
- { SETTING_SECTIONS . map ( ( section , idx ) => (
353
- < button
354
- key = { idx }
355
- className = { classNames ( {
356
- 'btn btn-ghost justify-start font-normal w-44 mb-1' : true ,
357
- 'btn-active' : sectionIdx === idx ,
358
- } ) }
359
- onClick = { ( ) => setSectionIdx ( idx ) }
360
- dir = "auto"
361
- >
362
- { section . title }
363
- </ button >
364
- ) ) }
365
- </ div >
359
+ < >
360
+ < ConfirmModal
361
+ isOpen = { showResetConfirm }
362
+ onClose = { handleResetCancel }
363
+ onConfirm = { handleResetConfirm }
364
+ message = "Are you sure you want to reset all settings?"
365
+ />
366
+ < AlertModal
367
+ isOpen = { alertState . isOpen }
368
+ onClose = { closeAlert }
369
+ message = { alertState . message }
370
+ />
371
+ < dialog
372
+ className = { classNames ( { modal : true , 'modal-open' : show } ) }
373
+ aria-label = "Settings dialog"
374
+ >
375
+ < div className = "modal-box w-11/12 max-w-3xl" >
376
+ < h3 className = "text-lg font-bold mb-6" > Settings</ h3 >
377
+ < div className = "flex flex-col md:flex-row h-[calc(90vh-12rem)]" >
378
+ { /* Left panel, showing sections - Desktop version */ }
379
+ < div
380
+ className = "hidden md:flex flex-col items-stretch pr-4 mr-4 border-r-2 border-base-200"
381
+ role = "complementary"
382
+ aria-description = "Settings sections"
383
+ tabIndex = { 0 }
384
+ >
385
+ { SETTING_SECTIONS . map ( ( section , idx ) => (
386
+ < button
387
+ key = { idx }
388
+ className = { classNames ( {
389
+ 'btn btn-ghost justify-start font-normal w-44 mb-1' : true ,
390
+ 'btn-active' : sectionIdx === idx ,
391
+ } ) }
392
+ onClick = { ( ) => setSectionIdx ( idx ) }
393
+ dir = "auto"
394
+ >
395
+ { section . title }
396
+ </ button >
397
+ ) ) }
398
+ </ div >
366
399
367
- { /* Left panel, showing sections - Mobile version */ }
368
- { /* This menu is skipped on a11y, otherwise it's repeated the desktop version */ }
369
- < div
370
- className = "md:hidden flex flex-row gap-2 mb-4"
371
- aria-disabled = { true }
372
- >
373
- < details className = "dropdown" >
374
- < summary className = "btn bt-sm w-full m-1" >
375
- { SETTING_SECTIONS [ sectionIdx ] . title }
376
- </ summary >
377
- < ul className = "menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow" >
378
- { SETTING_SECTIONS . map ( ( section , idx ) => (
379
- < div
380
- key = { idx }
381
- className = { classNames ( {
382
- 'btn btn-ghost justify-start font-normal' : true ,
383
- 'btn-active' : sectionIdx === idx ,
384
- } ) }
385
- onClick = { ( ) => setSectionIdx ( idx ) }
386
- dir = "auto"
387
- >
388
- { section . title }
389
- </ div >
390
- ) ) }
391
- </ ul >
392
- </ details >
393
- </ div >
400
+ { /* Left panel, showing sections - Mobile version */ }
401
+ { /* This menu is skipped on a11y, otherwise it's repeated the desktop version */ }
402
+ < div
403
+ className = "md:hidden flex flex-row gap-2 mb-4"
404
+ aria-disabled = { true }
405
+ >
406
+ < details className = "dropdown" >
407
+ < summary className = "btn bt-sm w-full m-1" >
408
+ { SETTING_SECTIONS [ sectionIdx ] . title }
409
+ </ summary >
410
+ < ul className = "menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow" >
411
+ { SETTING_SECTIONS . map ( ( section , idx ) => (
412
+ < div
413
+ key = { idx }
414
+ className = { classNames ( {
415
+ 'btn btn-ghost justify-start font-normal' : true ,
416
+ 'btn-active' : sectionIdx === idx ,
417
+ } ) }
418
+ onClick = { ( ) => setSectionIdx ( idx ) }
419
+ dir = "auto"
420
+ >
421
+ { section . title }
422
+ </ div >
423
+ ) ) }
424
+ </ ul >
425
+ </ details >
426
+ </ div >
394
427
395
- { /* Right panel, showing setting fields */ }
396
- < div className = "grow overflow-y-auto px-4" >
397
- { SETTING_SECTIONS [ sectionIdx ] . fields . map ( ( field , idx ) => {
398
- const key = `${ sectionIdx } -${ idx } ` ;
399
- if ( field . type === SettingInputType . SHORT_INPUT ) {
400
- return (
401
- < SettingsModalShortInput
402
- key = { key }
403
- configKey = { field . key }
404
- value = { localConfig [ field . key ] }
405
- onChange = { onChange ( field . key ) }
406
- label = { field . label as string }
407
- />
408
- ) ;
409
- } else if ( field . type === SettingInputType . LONG_INPUT ) {
410
- return (
411
- < SettingsModalLongInput
412
- key = { key }
413
- configKey = { field . key }
414
- value = { localConfig [ field . key ] . toString ( ) }
415
- onChange = { onChange ( field . key ) }
416
- label = { field . label as string }
417
- />
418
- ) ;
419
- } else if ( field . type === SettingInputType . CHECKBOX ) {
420
- return (
421
- < SettingsModalCheckbox
422
- key = { key }
423
- configKey = { field . key }
424
- value = { ! ! localConfig [ field . key ] }
425
- onChange = { onChange ( field . key ) }
426
- label = { field . label as string }
427
- />
428
- ) ;
429
- } else if ( field . type === SettingInputType . CUSTOM ) {
430
- return (
431
- < div key = { key } className = "mb-2" >
432
- { typeof field . component === 'string'
433
- ? field . component
434
- : field . component ( {
435
- value : localConfig [ field . key ] ,
436
- onChange : onChange ( field . key ) ,
437
- } ) }
438
- </ div >
439
- ) ;
440
- }
441
- } ) }
428
+ { /* Right panel, showing setting fields */ }
429
+ < div className = "grow overflow-y-auto px-4" >
430
+ { SETTING_SECTIONS [ sectionIdx ] . fields . map ( ( field , idx ) => {
431
+ const key = `${ sectionIdx } -${ idx } ` ;
432
+ if ( field . type === SettingInputType . SHORT_INPUT ) {
433
+ return (
434
+ < SettingsModalShortInput
435
+ key = { key }
436
+ configKey = { field . key }
437
+ value = { localConfig [ field . key ] }
438
+ onChange = { onChange ( field . key ) }
439
+ label = { field . label as string }
440
+ />
441
+ ) ;
442
+ } else if ( field . type === SettingInputType . LONG_INPUT ) {
443
+ return (
444
+ < SettingsModalLongInput
445
+ key = { key }
446
+ configKey = { field . key }
447
+ value = { localConfig [ field . key ] . toString ( ) }
448
+ onChange = { onChange ( field . key ) }
449
+ label = { field . label as string }
450
+ />
451
+ ) ;
452
+ } else if ( field . type === SettingInputType . CHECKBOX ) {
453
+ return (
454
+ < SettingsModalCheckbox
455
+ key = { key }
456
+ configKey = { field . key }
457
+ value = { ! ! localConfig [ field . key ] }
458
+ onChange = { onChange ( field . key ) }
459
+ label = { field . label as string }
460
+ />
461
+ ) ;
462
+ } else if ( field . type === SettingInputType . CUSTOM ) {
463
+ return (
464
+ < div key = { key } className = "mb-2" >
465
+ { typeof field . component === 'string'
466
+ ? field . component
467
+ : field . component ( {
468
+ value : localConfig [ field . key ] ,
469
+ onChange : onChange ( field . key ) ,
470
+ } ) }
471
+ </ div >
472
+ ) ;
473
+ }
474
+ } ) }
442
475
443
- < p className = "opacity-40 mb-6 text-sm mt-8" >
444
- Settings are saved in browser's localStorage
445
- </ p >
476
+ < p className = "opacity-40 mb-6 text-sm mt-8" >
477
+ Settings are saved in browser's localStorage
478
+ </ p >
479
+ </ div >
446
480
</ div >
447
- </ div >
448
481
449
- < div className = "modal-action" >
450
- < button className = "btn" onClick = { resetConfig } >
451
- Reset to default
452
- </ button >
453
- < button className = "btn" onClick = { onClose } >
454
- Close
455
- </ button >
456
- < button className = "btn btn-primary" onClick = { handleSave } >
457
- Save
458
- </ button >
482
+ < div className = "modal-action" >
483
+ < button className = "btn" onClick = { resetConfig } >
484
+ Reset to default
485
+ </ button >
486
+ < button className = "btn" onClick = { onClose } >
487
+ Close
488
+ </ button >
489
+ < button className = "btn btn-primary" onClick = { handleSave } >
490
+ Save
491
+ </ button >
492
+ </ div >
459
493
</ div >
460
- </ div >
461
- </ dialog >
494
+ </ dialog >
495
+ </ >
462
496
) ;
463
497
}
464
498
0 commit comments