Saltar al contenido
ssntl/ui

Toast

Una notificación flotante que se desliza para descartar: sigue al dedo y solo se va al soltar.

    Instalación

    $ pnpm dlx shadcn@latest add @ssntl/toast

    Uso

    tsx
    import { Toast, ToastAction, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from "@/components/ui/toast"
    
    <ToastProvider swipeDirection="right" label="Notificación">
      <Toast open={open} onOpenChange={setOpen} closeLabel="Descartar">
        <ToastTitle>Pedido archivado</ToastTitle>
        <ToastDescription>Pasó a Archivo con su historial.</ToastDescription>
        <ToastAction altText="Abre Archivo para restaurarlo">Deshacer</ToastAction>
      </Toast>
      <ToastViewport />
    </ToastProvider>

    Reglas

    Lo que este componente espera de ti, tal como se instala en components/ui/toast.md junto al código. El archivo completo agrega cuándo usarlo, teclado, los fundamentos de UI que aplica y cada regla compartida (en inglés).

    components/ui/toast.md
    ## Reglas de Toast
    
    - **Notificación flotante.** Elevation-4, sobre la página y sus menús; se anuncia con cortesía, se pausa al pasar el cursor o con foco y se alcanza con F8.
    - **Gesto inmediato.** El toast sigue al dedo 1:1 desde el primer píxel.
    - **Gesto continuo.** El puntero sigue capturado aunque salga del toast.
    - **Gesto cancelable.** Se descarta al soltar pasado el umbral; si sueltas antes, regresa.
    - Como máximo una acción, con `altText` para quien no alcance el toast a tiempo.
    - Con una acción o un error queda hasta que se descarte (duration={Infinity}); una confirmación simple dura al menos 5 segundos.
    - El botón de cierre dice “Dismiss notification” por defecto; cambia `closeLabel` en otros idiomas.
    Abrir el archivo de reglas completo (se abre en otra pestaña)

    Código

    components/ui/toast.tsx
    "use client"
    
    import * as React from "react"
    import { Toast as ToastPrimitive } from "radix-ui"
    
    import { cn } from "@/lib/utils"
    import { CloseButton } from "@/registry/ssntl/ui/close-button"
    
    // A floating notification (Radix Toast): announced politely, paused while
    // hovered or focused, and reachable with F8. Floating notification:
    // elevation 4, above the page and its menus.
    // Gestures: swipe right to dismiss. The toast follows the finger 1:1 from
    // the first pixel (responsive), keeps the pointer even off the toast
    // (continuous) and only dismisses on release past the threshold; letting go
    // short of it springs back (cancelable).
    const ToastProvider = ToastPrimitive.Provider
    
    function ToastViewport({
      className,
      ...props
    }: React.ComponentProps<typeof ToastPrimitive.Viewport>) {
      return (
        <ToastPrimitive.Viewport
          data-slot="toast-viewport"
          className={cn(
            "fixed inset-x-4 bottom-4 z-50 mx-auto grid max-w-sm gap-2 outline-none",
            className
          )}
          {...props}
        />
      )
    }
    
    function Toast({
      className,
      children,
      closeLabel = "Dismiss notification",
      ...props
    }: React.ComponentProps<typeof ToastPrimitive.Root> & {
      /** The close button's accessible name. */
      closeLabel?: string
    }) {
      return (
        <ToastPrimitive.Root
          data-slot="toast"
          className={cn(
            "relative grid touch-pan-y gap-1 rounded-lg bg-popover p-5 pe-16 text-left text-popover-foreground elevation-4 select-none",
            "data-[state=closed]:pop-out data-[state=open]:pop-in",
            "data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=move]:transition-none",
            "data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-[translate] data-[swipe=cancel]:duration-(--duration-menu) data-[swipe=cancel]:ease-out",
            "data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=end]:pop-out",
            className
          )}
          {...props}
        >
          {children}
          <ToastPrimitive.Close asChild>
            <CloseButton label={closeLabel} className="absolute end-2 top-2" />
          </ToastPrimitive.Close>
        </ToastPrimitive.Root>
      )
    }
    
    function ToastTitle({
      className,
      ...props
    }: React.ComponentProps<typeof ToastPrimitive.Title>) {
      return (
        <ToastPrimitive.Title
          data-slot="toast-title"
          className={cn("text-label", className)}
          {...props}
        />
      )
    }
    
    function ToastDescription({
      className,
      ...props
    }: React.ComponentProps<typeof ToastPrimitive.Description>) {
      return (
        <ToastPrimitive.Description
          data-slot="toast-description"
          className={cn(
            "text-caption text-pretty text-muted-foreground",
            className
          )}
          {...props}
        />
      )
    }
    
    /** One follow-up action, such as Undo; altText says how to do it otherwise. */
    function ToastAction({
      className,
      ...props
    }: React.ComponentProps<typeof ToastPrimitive.Action>) {
      return (
        <ToastPrimitive.Action
          data-slot="toast-action"
          className={cn(
            "mt-2 inline-flex h-9 w-fit cursor-pointer items-center rounded-full bg-secondary px-4 text-caption font-medium",
            "transition-[background-color,scale] duration-(--duration-fast) ease-out hover:bg-neutral-200 active:scale-(--es-press) motion-reduce:active:scale-100 dark:hover:bg-neutral-600",
            "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
            className
          )}
          {...props}
        />
      )
    }
    
    export {
      Toast,
      ToastAction,
      ToastDescription,
      ToastProvider,
      ToastTitle,
      ToastViewport,
    }