diff --git a/.changeset/visible-attachment-metadata.md b/.changeset/visible-attachment-metadata.md new file mode 100644 index 000000000..a0d6689b1 --- /dev/null +++ b/.changeset/visible-attachment-metadata.md @@ -0,0 +1,5 @@ +--- +"@truefoundry/trueforge-ui": patch +--- + +Show file-type icons, filenames, and sizes for composer and sent message attachments. diff --git a/packages/trueforge-ui/src/atoms/AttachmentCard.tsx b/packages/trueforge-ui/src/atoms/AttachmentCard.tsx index 5ea2feec9..651446147 100644 --- a/packages/trueforge-ui/src/atoms/AttachmentCard.tsx +++ b/packages/trueforge-ui/src/atoms/AttachmentCard.tsx @@ -14,6 +14,7 @@ export type AttachmentCardSize = 'chip' | 'preview'; export type AttachmentCardProps = { name: string; contentType?: string; + sizeBytes?: number; previewSrc?: string; isImage?: boolean; size?: AttachmentCardSize; @@ -23,8 +24,35 @@ export type AttachmentCardProps = { className?: string; }; +const FILE_ICON_BY_EXTENSION: Record = { + pdf: 'file-text', + ppt: 'file-text', + pptx: 'file-text', + doc: 'file-text', + docx: 'file-text', + txt: 'file-text', + csv: 'file-spreadsheet', + xls: 'file-spreadsheet', + xlsx: 'file-spreadsheet', + json: 'file-code', +}; + +export function getAttachmentFileIconName(name: string): string { + const dotIndex = name.lastIndexOf('.'); + const extension = dotIndex > 0 && dotIndex < name.length - 1 ? name.slice(dotIndex + 1).toLowerCase() : undefined; + return extension == null ? 'file' : (FILE_ICON_BY_EXTENSION[extension] ?? 'file'); +} + +function formatFileSize(bytes: number): string { + if (bytes === 0) return '0 B'; + const megabytes = bytes / (1024 * 1024); + if (megabytes < 1) return `${(bytes / 1024).toFixed(2)} KB`; + return `${megabytes.toFixed(2)} MB`; +} + export function AttachmentCard({ name, + sizeBytes, previewSrc, isImage = false, size = 'chip', @@ -50,24 +78,46 @@ export function AttachmentCard({ ); } + const imageChip = isImage && previewSrc != null; + const fileIcon = getAttachmentFileIconName(name); + return (
- -
- - - {/* bg-none drops the default gradient (bg-image group), which bg-secondary-bg alone would not override. */} - - - - -
-
- {name} + {imageChip ? ( + <> +
+ + + + + + +
+ + ) : ( + <> + +
+ + {name} + + {sizeBytes != null ? ( + {formatFileSize(sizeBytes)} + ) : null} +
+ + )} {onRemove && (