Announcement — Corner Card
blockmarketingCorner announcement card that slides in after a delay carrying a thumbnail, headline, supporting line, and two actions, staying out of the page flow entirely.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/announcement-corner-toast.json$npx shadcn@latest add https://uipkge.dev/r/react/announcement-corner-toast.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/announcement-corner-toast.json$bunx shadcn@latest add https://uipkge.dev/r/react/announcement-corner-toast.jsonNamed registry:
npx shadcn@latest add @uipkge-react/announcement-corner-toastInstalls to:components/blocks/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
delayMilliseconds before the card slides in. | number | — | optional |
storageKeysessionStorage key holding the dismissal. | string | — | optional |
npm dependencies
Files installed (1)
components/blocks/AnnouncementCornerToast.tsx3.3 kB
'use client' import { useEffect, useState } from 'react' import { ArrowRight, Bell, X } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' interface AnnouncementCornerToastProps { /** Milliseconds before the card slides in. */ delay?: number /** sessionStorage key holding the dismissal. */ storageKey?: string } export function AnnouncementCornerToast({ delay = 1200, storageKey = 'uipkge:corner-announcement-dismissed', }: AnnouncementCornerToastProps) { // Hidden on the server and on first paint; the delay timer is what reveals it. const [visible, setVisible] = useState(false) useEffect(() => { const wasDismissed = () => { try { return window.sessionStorage.getItem(storageKey) === '1' } catch { return false } } if (wasDismissed()) return const timer = setTimeout(() => setVisible(true), delay) return () => clearTimeout(timer) }, [delay, storageKey]) function dismiss() { setVisible(false) try { window.sessionStorage.setItem(storageKey, '1') } catch { // Blocked storage: the dismissal still holds for this page view. } } return ( <div data-slot="announcement-corner-toast" className={`fixed right-4 bottom-4 z-50 w-[22rem] max-w-[calc(100vw-2rem)] transition duration-300 ${ visible ? 'translate-y-0 opacity-100' : 'pointer-events-none invisible translate-y-4 opacity-0' }`} role="complementary" aria-label="Product announcement" aria-hidden={!visible} > <Card className="shadow-lg"> <CardContent className="p-4"> <div className="flex items-start gap-3"> <span className="border-border bg-muted flex size-10 shrink-0 items-center justify-center rounded-lg border" aria-hidden="true" > <Bell className="text-primary size-4" /> </span> <div className="min-w-0 flex-1"> <Badge variant="secondary" className="mb-1.5"> New </Badge> <p className="text-sm font-semibold">Scoping now follows SCIM groups</p> <p className="text-muted-foreground mt-1 text-xs leading-relaxed"> Move someone between teams in your IdP and their dashboards follow within the sync window. </p> </div> <Button variant="ghost" size="icon" className="-mt-1 -mr-1 size-7 shrink-0" aria-label="Dismiss announcement" onClick={dismiss} > <X className="size-3.5" aria-hidden="true" /> </Button> </div> <Separator className="my-3" /> <div className="flex items-center gap-2"> <Button size="sm" className="h-7"> Read the changelog <ArrowRight className="ml-1 size-3.5" aria-hidden="true" /> </Button> <Button variant="ghost" size="sm" className="h-7" onClick={dismiss}> Not now </Button> </div> </CardContent> </Card> </div> ) }
Raw manifest:https://uipkge.dev/r/react/announcement-corner-toast.json