Components
Attachment
A file/media attachment chip, typically used inside Message. Purely presentational.
Installation
npx kosmesis add attachmentpnpm dlx kosmesis add attachmentyarn dlx kosmesis add attachmentbunx kosmesis add attachmentInstall the following dependencies:
npm install @morphos/iconspnpm add @morphos/iconsyarn add @morphos/iconsbun add @morphos/iconsCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { cn } from "@/lib/utils";export interface AttachmentProps { name: string; size?: string; onRemove?: () => void; class?: string; children?: Children;}@Component()export class Attachment extends StatelessComponent<AttachmentProps> { render() { const { name, size, onRemove, class: cls, children } = this.props; return ( <div class={cn( "flex items-center gap-2 rounded-md border bg-muted/50 px-3 py-2 text-sm", cls, )} > <span class="flex size-8 shrink-0 items-center justify-center rounded bg-background text-muted-foreground"> {children ?? <Icon name="Paperclip" size={16} />} </span> <div class="flex min-w-0 flex-1 flex-col"> <span class="truncate font-medium">{name}</span> {size && <span class="text-xs text-muted-foreground">{size}</span>} </div> {onRemove && ( <button type="button" aria-label={`Remove ${name}`} class="shrink-0 text-muted-foreground transition-colors hover:text-foreground" onClick={onRemove} > <Icon name="X" size={14} /> </button> )} </div> ); }}export interface AttachmentGroupProps { class?: string; children?: Children;}@Component()export class AttachmentGroup extends StatelessComponent<AttachmentGroupProps> { render() { const { class: cls, children } = this.props; return <div class={cn("flex flex-wrap gap-2", cls)}>{children}</div>; }}Install the following dependencies:
npm install @praxisjs/css @morphos/iconspnpm add @praxisjs/css @morphos/iconsyarn add @praxisjs/css @morphos/iconsbun add @praxisjs/css @morphos/iconsCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class AttachmentStyles extends Stylesheet { $root = this.css({ display: "flex", alignItems: "center", gap: "0.5rem", borderRadius: `calc(${t.radius} - 2px)`, border: `1px solid ${t.border}`, backgroundColor: `color-mix(in oklab, ${t.muted} 50%, transparent)`, padding: "0.5rem 0.75rem", fontSize: "0.875rem", }); $icon = this.css({ display: "flex", width: "2rem", height: "2rem", flexShrink: 0, alignItems: "center", justifyContent: "center", borderRadius: "0.25rem", backgroundColor: t.background, color: t.mutedForeground, }); $info = this.css({ display: "flex", minWidth: "0", flex: "1 1 0%", flexDirection: "column" }); $name = this.css({ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontWeight: 500 }); $size = this.css({ fontSize: "0.75rem", color: t.mutedForeground }); $remove = this.css({ flexShrink: 0, color: t.mutedForeground, transition: "color 120ms ease" }).hover({ color: t.foreground }); $group = this.css({ display: "flex", flexWrap: "wrap", gap: "0.5rem" });}export interface AttachmentProps { name: string; size?: string; onRemove?: () => void; class?: string; children?: Children;}@Component()export class Attachment extends StatelessComponent<AttachmentProps> { @Styled(AttachmentStyles) $s!: AttachmentStyles; render() { const { name, size, onRemove, class: cls, children } = this.props; return ( <div class={cx(this.$s.$root, cls)}> <span class={this.$s.$icon}>{children ?? <Icon name="Paperclip" size={16} />}</span> <div class={this.$s.$info}> <span class={this.$s.$name}>{name}</span> {size && <span class={this.$s.$size}>{size}</span>} </div> {onRemove && ( <button type="button" aria-label={`Remove ${name}`} class={this.$s.$remove} onClick={onRemove}> <Icon name="X" size={14} /> </button> )} </div> ); }}export interface AttachmentGroupProps { class?: string; children?: Children;}@Component()export class AttachmentGroup extends StatelessComponent<AttachmentGroupProps> { @Styled(AttachmentStyles) $s!: AttachmentStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$group, cls)}>{children}</div>; }}Examples
Usage
import { Attachment, AttachmentGroup } from "@/components/ui/attachment";
<AttachmentGroup>
<Attachment name="invoice.pdf" size="248 KB" onRemove={() => {}} />
<Attachment name="photo.png" size="1.2 MB" onRemove={() => {}} />
</AttachmentGroup>Props
Attachment
| Prop | Type | Default |
|---|---|---|
name | string | — required |
size | string | — display-only size label (e.g. "248 KB") |
onRemove | () => void | — when set, renders a remove (X icon) button |
children | Children | <Icon name="Paperclip" /> — overrides the default icon slot |
AttachmentGroup takes no props beyond class/children.