Attachment
attachmentuiSingle file or image chip. Drive look with props: state, size, orientation, media, src, removable. Not a dropzone (see file-upload).
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/attachment.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/attachment.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/attachment.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/attachment.jsonManifest:
https://uipkge.dev/r/svelte/attachment.jsonInstalls to:src/lib/components/ui/attachment/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
size | 'default''sm''xs' | default | optional |
orientation | 'horizontal''vertical' | horizontal | optional |
title | string | — | required |
description | string | — | optional |
state | 'idle' | 'uploading' | 'processing' | 'error' | 'done' | done | optional |
media | 'file' | 'image' | 'code' | file | optional |
src | string | — | optional |
alt | string | — | optional |
removable | boolean | false | optional |
ref | HTMLDivElement | null | — | optional |
onRemove | () => void | — | optional |
npm dependencies
Files installed (3)
src/lib/components/ui/attachment/Attachment.svelte2.6 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface AttachmentProps extends HTMLAttributes<HTMLDivElement> { title: string description?: string state?: 'idle' | 'uploading' | 'processing' | 'error' | 'done' size?: 'default' | 'sm' | 'xs' orientation?: 'horizontal' | 'vertical' media?: 'file' | 'image' | 'code' src?: string alt?: string removable?: boolean ref?: HTMLDivElement | null onRemove?: () => void } </script> <script lang="ts"> import { FileCode, FileText, Image as ImageIcon, LoaderCircle, X } from '@lucide/svelte' import { cn } from '$lib/utils' import { attachmentMediaVariants, attachmentVariants } from './attachment.variants' let { title, description, state = 'done', size = 'default', orientation = 'horizontal', media = 'file', src, alt = '', removable = false, class: className, ref = $bindable(null), onRemove, ...restProps }: AttachmentProps = $props() const busy = $derived(state === 'uploading' || state === 'processing') </script> <div bind:this={ref} data-uipkge="" data-slot="attachment" data-state={state} data-size={size} data-orientation={orientation} class={cn(attachmentVariants({ size, orientation }), className)} {...restProps} > <div data-slot="attachment-media" class={cn(attachmentMediaVariants({ size }))}> {#if src && media === 'image' && !busy} <img {src} {alt} class="size-full object-cover" /> {:else if busy} <LoaderCircle class="size-4 motion-safe:animate-spin" aria-hidden="true" /> {:else if media === 'code'} <FileCode aria-hidden="true" /> {:else if media === 'image'} <ImageIcon aria-hidden="true" /> {:else} <FileText aria-hidden="true" /> {/if} </div> <div data-slot="attachment-content" class="min-w-0 flex-1 leading-tight"> <span data-slot="attachment-title" class={cn('block truncate font-medium', busy && 'animate-pulse')}> {title} </span> {#if description} <span data-slot="attachment-description" class={cn('text-muted-foreground mt-0.5 block truncate text-xs', state === 'error' && 'text-destructive/80')} > {description} </span> {/if} </div> {#if removable} <button type="button" data-slot="attachment-remove" class="text-muted-foreground hover:bg-accent hover:text-foreground relative z-10 inline-flex size-7 shrink-0 items-center justify-center rounded-md" aria-label={`Remove ${title}`} onclick={() => onRemove?.()} > <X class="size-3.5" /> </button> {/if} </div>src/lib/components/ui/attachment/attachment.variants.ts1.7 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts`) so consuming Vue SFCs can import without creating a circular * dependency through the index. See card.variants.ts for the canonical * example + the SSR symptom that motivated the split. */ export const attachmentVariants = cva( 'group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 rounded-xl border bg-card text-card-foreground transition-colors data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed', { variants: { size: { default: 'gap-2 p-2 text-sm', sm: 'gap-2 p-1.5 text-xs', xs: 'gap-1.5 rounded-lg p-1 text-xs', }, orientation: { horizontal: 'min-w-40 items-center', vertical: 'w-24 flex-col', }, }, defaultVariants: { size: 'default', orientation: 'horizontal', }, }, ) export const attachmentMediaVariants = cva( "relative flex aspect-square shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4", { variants: { size: { default: 'w-10', sm: 'w-8', xs: 'w-7 rounded-md [&_svg:not([class*="size-"])]:size-3.5', }, }, defaultVariants: { size: 'default', }, }, ) export type AttachmentVariants = VariantProps<typeof attachmentVariants>src/lib/components/ui/attachment/index.ts0.2 kB
export { default as Attachment, type AttachmentProps } from './Attachment.svelte' export { attachmentVariants, attachmentMediaVariants, type AttachmentVariants } from './attachment.variants'
Raw manifest:https://uipkge.dev/r/svelte/attachment.json