mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: add notification optimization with visibility tracking and toast messages
Implement a visibility-aware notification system that delivers toast messages via SSE to visible browser tabs instead of sending push notifications, reducing unnecessary push requests. Includes VisibilityTracker for monitoring connection visibility, toast UI components, and enhanced SSEManager with toast delivery capability.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { Toast } from '@/components/ui/Toast'
|
||||
import { useToast } from '@/lib/toast-context'
|
||||
|
||||
export function ToastContainer() {
|
||||
const navigate = useNavigate()
|
||||
const { toasts, removeToast } = useToast()
|
||||
|
||||
if (toasts.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none fixed inset-x-0 top-[calc(env(safe-area-inset-top)+1rem)] z-50 flex flex-col items-center gap-2 px-3"
|
||||
aria-live="polite"
|
||||
>
|
||||
{toasts.map((toast) => (
|
||||
<Toast
|
||||
key={toast.id}
|
||||
title={toast.title}
|
||||
body={toast.body}
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
removeToast(toast.id)
|
||||
if (toast.sessionId) {
|
||||
void navigate({
|
||||
to: '/sessions/$sessionId',
|
||||
params: { sessionId: toast.sessionId }
|
||||
})
|
||||
return
|
||||
}
|
||||
if (toast.url) {
|
||||
void navigate({ to: toast.url })
|
||||
}
|
||||
}}
|
||||
onClose={() => removeToast(toast.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as React from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const toastVariants = cva(
|
||||
'pointer-events-auto w-full max-w-sm rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] text-[var(--app-fg)] shadow-lg',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-[var(--app-border)] bg-[var(--app-bg)]'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export type ToastProps = React.HTMLAttributes<HTMLDivElement> &
|
||||
VariantProps<typeof toastVariants> & {
|
||||
title: string
|
||||
body: string
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export function Toast({ title, body, onClose, className, variant, ...props }: ToastProps) {
|
||||
const handleClose = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation()
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(toastVariants({ variant }), className)} role="status" {...props}>
|
||||
<div className="flex items-start gap-3 p-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-semibold leading-5">{title}</div>
|
||||
<div className="mt-1 text-xs text-[var(--app-hint)]">{body}</div>
|
||||
</div>
|
||||
{onClose ? (
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-[var(--app-hint)] hover:text-[var(--app-fg)]"
|
||||
onClick={handleClose}
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
x
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user