feat: sign in with a passkey (WebAuthn, ES256, discoverable credentials) and manage your passkeys [CI]

This commit is contained in:
justme committed 2026-10-07 21:57:19 +08:00
1 parent ddb6608e32
commit 6385c1b9a3
31 files changed
+2296 -42

No files matched your search

+14 -5
View File
@@ -104,24 +104,33 @@ interface ToastItem {
id: number
text: string
bad: boolean
title?: string
}
const ToastContext = createContext<(text: string, bad?: boolean) => void>(() => undefined)
/** 提示的可选项:`title` 是加粗的标题行,`ms` 是停留的毫秒数(缺省成功 3 秒、失败 6 秒)。点一下提示可以提前关掉。 */
export interface ToastOptions {
title?: string
ms?: number
}
const ToastContext = createContext<(text: string, bad?: boolean, options?: ToastOptions) => void>(() => undefined)
export function ToastProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<ToastItem[]>([])
const nextId = useRef(1)
const push = useCallback((text: string, bad = false) => {
const push = useCallback((text: string, bad = false, options?: ToastOptions) => {
const id = nextId.current++
setItems((list) => [...list, { id, text, bad }])
window.setTimeout(() => setItems((list) => list.filter((t) => t.id !== id)), bad ? 6000 : 3000)
setItems((list) => [...list, { id, text, bad, title: options?.title }])
window.setTimeout(() => setItems((list) => list.filter((t) => t.id !== id)), options?.ms ?? (bad ? 6000 : 3000))
}, [])
const dismiss = (id: number) => setItems((list) => list.filter((t) => t.id !== id))
return (
<ToastContext.Provider value={push}>
{children}
<div className="toasts" aria-live="polite">
{items.map((t) => (
<div key={t.id} className={`toast ${t.bad ? 'bad' : ''}`}>
<div key={t.id} className={`toast ${t.bad ? 'bad' : ''}`} role="status" onClick={() => dismiss(t.id)} title="点一下关闭">
{t.title ? <div className="toast-title">{t.title}</div> : null}
{t.text}
</div>
))}