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

Install the following dependencies:

npm install @morphos/icons
pnpm add @morphos/icons
yarn add @morphos/icons
bun add @morphos/icons

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 { 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/icons
pnpm add @praxisjs/css @morphos/icons
yarn add @praxisjs/css @morphos/icons
bun add @praxisjs/css @morphos/icons

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 { 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

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

AttachmentGroup takes no props beyond class/children.

On this page