@@ -13,6 +13,7 @@ import {
1313 SquaresPlusIcon ,
1414} from '@heroicons/react/24/outline' ;
1515import { OpenInNewTab } from '../utils/common' ;
16+ import { ConfirmModal , AlertModal } from './CustomModals' ;
1617
1718type SettKey = keyof typeof CONFIG_DEFAULT ;
1819
@@ -277,16 +278,36 @@ export default function SettingDialog({
277278} ) {
278279 const { config, saveConfig } = useAppContext ( ) ;
279280 const [ sectionIdx , setSectionIdx ] = useState ( 0 ) ;
281+ const [ showResetConfirm , setShowResetConfirm ] = useState ( false ) ;
282+ const [ alertState , setAlertState ] = useState ( {
283+ isOpen : false ,
284+ message : '' ,
285+ } ) ;
280286
281287 // clone the config object to prevent direct mutation
282288 const [ localConfig , setLocalConfig ] = useState < typeof CONFIG_DEFAULT > (
283289 JSON . parse ( JSON . stringify ( config ) )
284290 ) ;
285291
286292 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 : '' } ) ;
290311 } ;
291312
292313 const handleSave = ( ) => {
@@ -302,22 +323,22 @@ export default function SettingDialog({
302323 const mustBeNumeric = isNumeric ( CONFIG_DEFAULT [ key as SettKey ] ) ;
303324 if ( mustBeString ) {
304325 if ( ! isString ( value ) ) {
305- alert ( `Value for ${ key } must be string` ) ;
326+ showAlert ( `Value for ${ key } must be string` ) ;
306327 return ;
307328 }
308329 } else if ( mustBeNumeric ) {
309330 const trimmedValue = value . toString ( ) . trim ( ) ;
310331 const numVal = Number ( trimmedValue ) ;
311332 if ( isNaN ( numVal ) || ! isNumeric ( numVal ) || trimmedValue . length === 0 ) {
312- alert ( `Value for ${ key } must be numeric` ) ;
333+ showAlert ( `Value for ${ key } must be numeric` ) ;
313334 return ;
314335 }
315336 // force conversion to number
316337 // @ts -expect-error this is safe
317338 newConfig [ key ] = numVal ;
318339 } else if ( mustBeBoolean ) {
319340 if ( ! isBoolean ( value ) ) {
320- alert ( `Value for ${ key } must be boolean` ) ;
341+ showAlert ( `Value for ${ key } must be boolean` ) ;
321342 return ;
322343 }
323344 } else {
@@ -335,130 +356,143 @@ export default function SettingDialog({
335356 } ;
336357
337358 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 >
366399
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 >
394427
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+ } ) }
442475
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 >
446480 </ div >
447- </ div >
448481
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 >
459493 </ div >
460- </ div >
461- </ dialog >
494+ </ dialog >
495+ </ >
462496 ) ;
463497}
464498
0 commit comments