<dialog>
元素看到tl;博士
使用内置
dialog.showModal()
会导致 <dialog>
元素始终位于顶部,因此 Toast 通知隐藏在 ::backdrop
后面。
不久前,
<dialog>
元素获得了一些很好的功能,可以使用 JavaScript 轻松显示和关闭它。它还附带了一个新的 ::backdrop
伪元素,用于在对话框打开时设置背景覆盖层的样式。
我的大多数项目都使用 Next.js 和 Tailwind CSS。因为 Next.js 是基于 React 构建的,所以我可以利用 Tailwind Labs 的 Headless UI 包,并且我之前使用过他们的
<Dialog>
组件而不是 <dialog>
元素。然而,在这个项目中,我决定尝试内置的 <dialog>
元素功能,因为它具有辅助功能。
在本例中,我使用对话框允许用户更改密码。当然,向用户显示通知非常重要,让他们知道密码是否已成功更改。当然,我正在使用 React Hot Toast 来处理这些通知。在之前的项目中,当使用 Headless UI 的
<Dialog>
组件时,我在显示这些通知时没有遇到任何问题。
使用内置的
<dialog>
元素,通知会停留在其后面。如果 ::backdrop
伪元素是透明的,这不是一个大问题,但如果是透明的,它看起来总是更好。
我已经测试过通过 Tailwind 类和
z-index
属性更改 <dialog>
元素和 <Toaster>
组件上的 style
。即使使用极端值,这两种方法都不起作用。
请记住,我正在使用应用程序路由器。
<RootLayout>
<Toaster
position='top-right'
toastOptions={{
duration: 3000
}}
/>
<ChangePassword>
组件function ChangePassword({ username }: { username: string | undefined }) {
const changePasswordDialogRef = useRef<HTMLDialogElement>(null),
currentPasswordInputRef = useRef<HTMLInputElement>(null),
newPasswordInputRef = useRef<HTMLInputElement>(null),
formRef = useRef<HTMLFormElement>(null)
const currentPasswordValueRef = useRef(''),
newPasswordValueRef = useRef('')
const currentPasswordIsValidRef = useRef(false),
newPasswordIsValidRef = useRef(false)
const openPasswordChangeModal = () => changePasswordDialogRef.current?.showModal()
const closePasswordChangeModal = () => {
formRef.current?.reset()
changePasswordDialogRef.current?.close()
}
const [formSubmissionState, setFormSubmissionState] = useState<
'incomplete' | 'ready' | 'loading' | 'success' | 'error'
>('incomplete')
const validateInputs = useCallback(
(inputId: string, newValue: string) => {
if (
(inputId === 'currentPassword' && newValue.length >= 8) ||
(inputId !== 'currentPassword' &&
currentPasswordValueRef.current.length >= 8) ||
getComputedStyle(currentPasswordInputRef.current as Element).position === 'static'
) {
currentPasswordIsValidRef.current = true
} else {
currentPasswordIsValidRef.current = false
}
if (
(inputId === 'newPassword' && newValue.length >= 8) ||
(inputId !== 'newPassword' &&
newPasswordValueRef.current.length >= 8) ||
getComputedStyle(newPasswordInputRef.current as Element).position === 'static'
) {
newPasswordIsValidRef.current = true
} else {
newPasswordIsValidRef.current = false
}
const currentPasswordIsValid = currentPasswordIsValidRef.current,
newPasswordIsValid = newPasswordIsValidRef.current
const validInputs = [currentPasswordIsValid, newPasswordIsValid]
const inputsAreValid = validInputs.includes(false) ? false : true
if (inputsAreValid && formSubmissionState !== 'ready')
setFormSubmissionState('ready')
if (!inputsAreValid && formSubmissionState !== 'incomplete')
setFormSubmissionState('incomplete')
},
[formSubmissionState]
)
let inputChangeTimeout: NodeJS.Timeout
const handleInputChange: ChangeEventHandler<HTMLInputElement> = e => {
clearTimeout(inputChangeTimeout)
inputChangeTimeout = setTimeout(async () => {
const { id, value } = e.target
switch (id) {
case 'currentPassword':
currentPasswordValueRef.current = value
break
case 'newPassword':
newPasswordValueRef.current = value
break
default:
null
}
validateInputs(id, value)
}, 250)
}
const handleChangePassword = async () => {
const currentPassword = currentPasswordValueRef.current,
newPassword = newPasswordValueRef.current
if (currentPassword === newPassword) {
toast.error('You cannot set the new password to your old password.')
throw new Error('You cannot set the new password to your old password.')
}
return true
}
const toastChangePassword: FormEventHandler<HTMLFormElement> = e => {
e.preventDefault()
e.stopPropagation()
toast.promise(handleChangePassword(), {
loading: 'Saving Password...',
success: () => {
closePasswordChangeModal()
return 'Successfully changed password.'
},
error: 'Failed to change password. Please try again.'
})
}
return (
<>
<div className='w-fit sm:ml-auto -mt-2'>
<a
href='#top'
onClick={openPasswordChangeModal}
>
Change Password
</a>
</div>
<dialog
ref={changePasswordDialogRef}
className='
w-[calc(100%-2rem)] max-w-xs
rounded-3xl
shadow-xl dark:shadow-zinc-700
backdrop:bg-zinc-100/60 dark:backdrop:bg-zinc-900/60
backdrop:backdrop-blur-sm dark:backdrop:brightness-125
'
>
<form
ref={formRef}
onSubmit={toastChangePassword}
>
<input
ref={currentPasswordInputRef}
name='Current Password'
type='password'
placeholder='••••••••'
className='placeholder:font-black placeholder:tracking-widest autofill:static'
minLength={8}
onChange={handleInputChange}
autoComplete='old-password'
required
/>
<input
ref={newPasswordInputRef}
name='New Password'
type='password'
placeholder='••••••••'
className='placeholder:font-black placeholder:tracking-widest autofill:static'
minLength={8}
onChange={handleInputChange}
autoComplete='new-password'
required
/>
<button type='submit'>Update Password</button>
</form>
</dialog>
</>
)
}
我使用的软件包略有不同,但遇到了与我解决的
react-hot-toast
相同的问题。
我在 Next.js 应用程序中使用material-tailwind Dialog 和
react-hot-toast
。我发现将 containerStyle
组件中的 Toaster
属性设置为 {{zIndex: 99999}}
将 Toast 移动到 Dialog
背景模糊前面,以便 Toast 警报处于焦点位置。我尝试了 9999 来获取 zIndex
的值,但仍然不起作用,但 99999 对我有用。
react-hot-toast
样式可以在这里找到:https://react-hot-toast.com/docs/styling
对我有用的完整
Toaster
组件在这里,放置在 RootLayout
中的 layout.tsx
中:
<Toaster
position="bottom-left"
containerStyle={{zIndex: 99999}}
toastOptions={{
success: {
style: {
background: 'lightblue',
},
iconTheme: {
primary: 'white',
secondary: 'black',
}
},
error: {
style: {
background: 'palevioletred',
},
},
}}
/>