Sabil UI

Search documentation

Search for a command to run...

Components

Attachment

Displays a file, image or link attached to a message or form.

Workspace
workspace.pngPNG · 820 KB
Desk
desk-reference.jpgJPG · 1.1 MB
Office
office-reference.jpgJPG · 940 KB
sales-dashboard.pdfUploading · 64%
message-renderer.tsxTypeScript · 12 KB

Installation#

  1. Install the dependencies

    npm install class-variance-authority cn radix-ui
  2. Copy the source into your project

    components/ui/attachment.tsx
    import * as React from "react"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "cn"import { Slot } from "radix-ui"import { Button } from "@/components/ui/button"const attachmentVariants = cva(  "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",  {    variants: {      size: {        default:          "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2",        sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5",        xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1",      },      orientation: {        horizontal: "min-w-40 items-center",        vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30",      },    },  })function Attachment({  className,  state = "done",  size = "default",  orientation = "horizontal",  ...props}: React.ComponentProps<"div"> &  VariantProps<typeof attachmentVariants> & {    state?: "idle" | "uploading" | "processing" | "error" | "done"  }) {  return (    <div      data-slot="attachment"      data-state={state}      data-size={size}      data-orientation={orientation}      className={cn(attachmentVariants({ size, orientation }), className)}      {...props}    />  )}const attachmentMediaVariants = cva(  "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",  {    variants: {      variant: {        icon: "",        image:          "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover",      },    },    defaultVariants: {      variant: "icon",    },  })function AttachmentMedia({  className,  variant = "icon",  ...props}: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>) {  return (    <div      data-slot="attachment-media"      data-variant={variant}      className={cn(attachmentMediaVariants({ variant }), className)}      {...props}    />  )}function AttachmentContent({  className,  ...props}: React.ComponentProps<"div">) {  return (    <div      data-slot="attachment-content"      className={cn(        "max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",        className      )}      {...props}    />  )}function AttachmentTitle({  className,  ...props}: React.ComponentProps<"span">) {  return (    <span      data-slot="attachment-title"      className={cn(        "block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",        className      )}      {...props}    />  )}function AttachmentDescription({  className,  ...props}: React.ComponentProps<"span">) {  return (    <span      data-slot="attachment-description"      className={cn(        "mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",        "max-w-full",        className      )}      {...props}    />  )}function AttachmentActions({  className,  ...props}: React.ComponentProps<"div">) {  return (    <div      data-slot="attachment-actions"      className={cn(        "relative z-20 flex shrink-0 items-center group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 group-data-[orientation=vertical]/attachment:gap-1",        className      )}      {...props}    />  )}function AttachmentAction({  className,  variant,  size = "icon-xs",  ...props}: React.ComponentProps<typeof Button>) {  return (    <Button      data-slot="attachment-action"      variant={variant ?? "ghost"}      size={size}      className={cn(className)}      {...props}    />  )}function AttachmentTrigger({  className,  asChild = false,  type,  ...props}: React.ComponentProps<"button"> & {  asChild?: boolean}) {  const Comp = asChild ? Slot.Root : "button"  return (    <Comp      data-slot="attachment-trigger"      type={asChild ? undefined : (type ?? "button")}      className={cn("absolute inset-0 z-10 outline-none", className)}      {...props}    />  )}function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="attachment-group"      className={cn(        "flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",        className      )}      {...props}    />  )}export {  Attachment,  AttachmentGroup,  AttachmentMedia,  AttachmentContent,  AttachmentTitle,  AttachmentDescription,  AttachmentActions,  AttachmentAction,  AttachmentTrigger,}
  3. Make sure the foundation is in place

    Components read their colors, radius and surfaces from the tokens in app/globals.css. See Theming.

Usage#

import {  Attachment,  AttachmentGroup,  AttachmentMedia,  AttachmentContent,  AttachmentTitle,  AttachmentDescription,  AttachmentActions,  AttachmentAction,  AttachmentTrigger,} from "@/components/ui/attachment"

Examples#

Group

briefing-notes.pdfPDF · 1.4 MB
workspace.png
workspace.pngPNG · 820 KB
customers.csvCSV · 18 KB
renderer.tsxTSX · 12 KB

Image

Workspace
workspace.pngPNG · 820 KB
Desk
desk-reference.jpgJPG · 1.1 MB
Office
office-reference.jpgJPG · 940 KB

Sizes

Default attachmentPDF · 2.4 MB
Small attachmentPDF · 2.4 MB
Extra small attachment

States

selected-file.pdfReady to upload
design-system.zipUploading · 64%
market-research.pdfProcessing document
financial-model.xlsxUpload failed. Try again.
uploaded-report.pdfUploaded · 1.8 MB

Trigger

research-summary.pdfOpen preview dialog