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@latest add https://uipkge.dev/r/lit/attachment.json$npx shadcn@latest add https://uipkge.dev/r/lit/attachment.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/attachment.json$bunx shadcn@latest add https://uipkge.dev/r/lit/attachment.jsonManifest:
https://uipkge.dev/r/lit/attachment.jsonInstalls to:src/uip/components/attachment/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
title | string | — | optional |
description | string | — | optional |
state | State | done | optional |
size | 'default''sm''xs' | default | optional |
orientation | 'horizontal''vertical' | horizontal | optional |
media | Media | file | optional |
src | string | — | optional |
alt | string | — | optional |
removable | boolean | false | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-attachment': UipAttachment
}
}npm dependencies
Includes
Files installed (2)
src/uip/components/attachment/attachment.ts4.3 kB
import { LitElement, css, html, nothing } from 'lit' import { FileCode, FileText, Image, Loader2, X } from 'lucide' import { cn } from '../../lib/utils' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' import { icon } from '../../lib/icon' import { attachmentMediaVariants, attachmentVariants, type AttachmentVariants } from './attachment.variants' type Size = NonNullable<AttachmentVariants['size']> type Orientation = NonNullable<AttachmentVariants['orientation']> type State = 'idle' | 'uploading' | 'processing' | 'error' | 'done' type Media = 'file' | 'image' | 'code' /** * <uip-attachment> — file and media attachment chip. */ export class UipAttachment extends LitElement { static styles = [tailwind, css`:host { display: inline-flex; }`] static properties = { title: { reflect: true }, description: { reflect: true }, state: { reflect: true }, size: { reflect: true }, orientation: { reflect: true }, media: { reflect: true }, src: { reflect: true }, alt: { reflect: true }, removable: { type: Boolean, reflect: true }, } override title = '' description?: string state: State = 'done' size: Size = 'default' orientation: Orientation = 'horizontal' media: Media = 'file' src?: string alt = '' removable = false constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'attachment') } willUpdate() { this.setAttribute('data-state', this.state) this.setAttribute('data-size', this.size) this.setAttribute('data-orientation', this.orientation) } private onRemoveClick(e: MouseEvent) { e.stopPropagation() this.dispatchEvent(new CustomEvent('remove', { bubbles: true, composed: true })) } render() { const busy = this.state === 'uploading' || this.state === 'processing' const size = this.size const orientation = this.orientation const state = this.state const media = this.media let mediaContent if (this.src && media === 'image' && !busy) { mediaContent = html`<img src=${this.src} alt=${this.alt} class="size-full object-cover" />` } else if (busy) { mediaContent = icon(Loader2, 'loader-2', 'size-4 motion-safe:animate-spin') } else if (media === 'code') { mediaContent = icon(FileCode, 'file-code') } else if (media === 'image') { mediaContent = icon(Image, 'image') } else { mediaContent = icon(FileText, 'file-text') } return html` <div part="base" data-slot="attachment" data-state=${state} data-size=${size} data-orientation=${orientation} class=${cn(attachmentVariants({ size, orientation }))} > <div data-slot="attachment-media" class=${cn(attachmentMediaVariants({ size }))}> ${mediaContent} </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')}> ${this.title} </span> ${this.description ? html` <span data-slot="attachment-description" class=${cn( 'text-muted-foreground mt-0.5 block truncate text-xs', state === 'error' && 'text-destructive/80', )} > ${this.description} </span> ` : nothing} </div> ${this.removable ? html` <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 ${this.title}`} @click=${this.onRemoveClick} > ${icon(X, 'x', 'size-3.5')} </button> ` : nothing} </div> ` } } customElements.get('uip-attachment') || customElements.define('uip-attachment', UipAttachment) declare global { interface HTMLElementTagNameMap { 'uip-attachment': UipAttachment } }src/uip/components/attachment/attachment.variants.ts1.4 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' 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>
Raw manifest:https://uipkge.dev/r/lit/attachment.json