Attachment

Data Display

메시지에 첨부된 파일을 보여주는 카드.

Usage

사용자가 메시지에 첨부한 파일을 썸네일과 이름으로 보여줄 때 사용한다.

import

import

import { Attachment } from '@mildang/design-system/unofficial/Chat';

Attachment 하나만 가져오면 Attachment.Thumb · Attachment.Name · Attachment.Remove 를 그 아래에서 쓸 수 있다.

예제를 복사해 쓸 때 필요한 준비

@mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

외부 패키지를 따로 설치한다. DS 의 전이 의존성에 기대지 않는다.

Anatomy

tsx

import { Attachment } from '@mildang/design-system/unofficial/Chat';

export default function Example() {
  return (
    <Attachment.Thumb />
    <Attachment.Name />
    <Attachment.Remove />
  );
}

부품

필수 여부

반복

위치

Attachment.Thumb

필수

Attachment.Root 안에 둔다.

Attachment.Name

선택

Attachment.Root 안에 둔다.

Attachment.Remove

조건부

편집 가능한 컴포저 컨텍스트에서만 의미가 있다.

Attachment.Root 안에 둔다.

  • 업로드 진행률·오류 같은 상태는 Attachment.Thumb 주변에 별도 인디케이터로 조합한다.
  • 파일 확장자별 아이콘은 FileExtensionIcon과 함께 조합한다.

API Reference

Attachment Props

Prop

Type

Default

asChild

boolean

지정 안 함

render

ReactElement<unknown, string | JSXElementConstructor<any>>

지정 안 함

Attachment.Thumb

파일 종류에 맞는 썸네일 또는 확장자 아이콘을 보여준다.

공개 Props 없음

Attachment.Name

첨부 파일의 이름을 보여준다.

공개 Props 없음

Attachment.Remove

첨부 파일을 목록에서 제거하는 버튼이다.

Prop

Type

Default

asChild

boolean

지정 안 함

icon

ReactNode

지정 안 함

render

import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>

지정 안 함

같은 패밀리

@mildang/design-system/unofficial/Chat 에서 같이 내보내는 컴포넌트다.

예제

기본 사용

메시지 안에 첨부 파일 카드를 보여주는 예시다.

코드

// AttachmentDemo.example.tsx
import { css } from "@mildang/styled-system/css";
import { MessagePrimitive, ThreadPrimitive } from "@assistant-ui/react";
import { Attachment } from "@mildang/design-system/unofficial/Chat";

// ../../examples/mockRuntime.tsx
import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  useRemoteThreadListRuntime
} from "@assistant-ui/react";
import { useState } from "react";
var mockAdapter = {
  async run() {
    return {
      content: [{ type: "text", text: "안녕하세요! 무엇을 도와드릴까요? (mock 응답)" }]
    };
  }
};
function MockRuntimeProvider({
  children,
  initialMessages
}) {
  const runtime = useLocalRuntime(mockAdapter, { initialMessages });
  return <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>;
}

// AttachmentDemo.example.tsx
var messages = [
  {
    role: "user",
    content: "첨부 파일 확인해줘",
    attachments: [
      {
        id: "att-1",
        type: "document",
        name: "report.pdf",
        contentType: "application/pdf",
        content: [],
        status: { type: "complete" }
      },
      {
        id: "att-2",
        type: "image",
        name: "chart.png",
        contentType: "image/png",
        content: [],
        status: { type: "complete" }
      }
    ]
  }
];
var AttachmentCard = () => <Attachment>
    <Attachment.Thumb />
    <Attachment.Name />
    <Attachment.Remove />
  </Attachment>;
var UserMessage = () => <MessagePrimitive.Root>
    <div className={css({ display: "flex", flexWrap: "wrap", gap: "8", justifyContent: "flex-end" })}>
      <MessagePrimitive.Attachments components={{ Attachment: AttachmentCard }} />
    </div>
  </MessagePrimitive.Root>;
var AssistantMessage = () => null;
var wrapper = css({ padding: "16", display: "flex", flexDirection: "column" });
var AttachmentDemoExample = () => <MockRuntimeProvider initialMessages={messages}>
    <div className={wrapper}>
      <ThreadPrimitive.Root>
        <ThreadPrimitive.Messages components={{ UserMessage, AssistantMessage }} />
      </ThreadPrimitive.Root>
    </div>
  </MockRuntimeProvider>;
var AttachmentDemo_example_default = AttachmentDemoExample;
export {
  AttachmentDemo_example_default as default
};

확장자 썸네일

파일 확장자별로 다른 아이콘 썸네일을 보여주는 예시다.

코드

import { css } from '@mildang/styled-system/css';
import { chatAttachment } from '@mildang/styled-system/recipes';
import { FileExtensionIcon } from '@mildang/design-system/FileExtensionIcon';

// 문서 타입 폴백: thumb 슬롯 안에 DS FileExtensionIcon 을 합성하는 구조. 실제 앱은 attachment.name 에서
// 확장자를 도출해 넘긴다.
const AttachmentFileExtensionThumbExample = () => {
  const classes = chatAttachment();
  return (
    <div className={css({ display: 'flex', flexWrap: 'wrap', gap: '8', padding: '16' })}>
      {(['pdf', 'xlsx', 'docx'] as const).map((extension, index) => (
        <div key={extension} className={classes.root}>
          <span className={classes.thumb}>
            <FileExtensionIcon extension={extension} />
          </span>
          <span>{['report.pdf', 'scores.xlsx', 'notice.docx'][index]}</span>
        </div>
      ))}
    </div>
  );
};

export default AttachmentFileExtensionThumbExample;

업로드 상태

업로드 진행 중·오류 상태를 함께 조합한 예시다.

코드

import { css } from '@mildang/styled-system/css';
import { chatAttachment } from '@mildang/styled-system/recipes';
import WarningIcon from '@mildang/icons/react/warning-fill';
import { FileExtensionIcon } from '@mildang/design-system/FileExtensionIcon';
import { Spinner } from '@mildang/design-system/Spinner';
import { Tooltip } from '@mildang/design-system/Tooltip';
import { IconButton } from '@mildang/design-system/IconButton';

// 업로드 상태 오버레이. Attachment.Root 가 `s.attachment.status` 를 구독해 uploading(DS Spinner) /
// error(경고 아이콘 + DS Tooltip) 를 칩 위에 덮는다. 오버레이는 pointerEvents:none 이라 Remove 버튼은
// 계속 눌린다. (여기서는 상태를 강제하기 위해 레시피를 직접 호출한다)
const AttachmentUploadStatusExample = () => {
  const base = chatAttachment();
  return (
    <div className={css({ display: 'flex', flexWrap: 'wrap', gap: '8', padding: '16' })}>
      <div className={base.root}>
        <span className={base.thumb}>
          <FileExtensionIcon extension="pdf" />
        </span>
        <span>report.pdf</span>
        <div className={chatAttachment({ status: 'uploading' }).overlay} role="status" aria-label="첨부 업로드 중">
          <Spinner size="xs" color="neutral" />
        </div>
      </div>
      <div className={base.root}>
        <span className={base.thumb}>
          <FileExtensionIcon extension="xlsx" />
        </span>
        <span>scores.xlsx</span>
        <div className={chatAttachment({ status: 'error' }).overlay}>
          <Tooltip content="파일 크기가 너무 큽니다">
            <IconButton
              variant="tertiary"
              size="xs"
              color="critical.icon.base"
              pointerEvents="auto"
              aria-label="파일 크기가 너무 큽니다"
            >
              <WarningIcon />
            </IconButton>
          </Tooltip>
        </div>
      </div>
    </div>
  );
};

export default AttachmentUploadStatusExample;