Kosmesis
Components

Attachment

A file/media attachment chip, typically used inside Message. Purely presentational.

Installation

npx kosmesis add attachment
pnpm dlx kosmesis add attachment
yarn dlx kosmesis add attachment
bunx kosmesis add attachment

Copy and paste the following code into your project.

attachment.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";export interface AttachmentProps {  name: string;  size?: string;  onRemove?: () => void;  class?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent. A file/media chip, typically used inside `Message`/`Bubble`. */@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 ?? "📎"}        </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}          >          </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
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

Copy and paste the following code into your project.

attachment.tsx
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 { 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;}/** Purely presentational — no Morphos equivalent. A file/media chip, typically used inside `Message`/`Bubble`. */@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 ?? "📎"}</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}>          </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

PropTypeDefault
namestring— required
sizestring— display-only size label (e.g. "248 KB")
onRemove() => void— when set, renders a remove (✕) button
childrenChildren"📎" — overrides the default icon slot

AttachmentGroup takes no props beyond class/children.

On this page