UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

1 lines 7.3 kB
{"version":3,"sources":["../src/components/FileAttachment/FileAttachment.tsx"],"sourcesContent":["import React, { useRef } from 'react';\nimport { PaperclipIcon } from '@phosphor-icons/react/dist/csr/Paperclip';\nimport { FileTextIcon } from '@phosphor-icons/react/dist/csr/FileText';\nimport { XIcon } from '@phosphor-icons/react/dist/csr/X';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * Represents an attached file with unique identifier\n */\nexport interface AttachedFile {\n /** The file object */\n file: File;\n /** Unique identifier for the file */\n id: string;\n}\n\n/**\n * Props for the FileAttachment component\n */\nexport interface FileAttachmentProps {\n /** List of attached files */\n files: AttachedFile[];\n /** Callback when files are added */\n onFilesAdd: (files: File[]) => void;\n /** Callback when a file is removed */\n onFileRemove: (id: string) => void;\n /** Whether the files are read-only (no removal allowed) */\n readOnly?: boolean;\n /** Text for the attach button */\n buttonLabel?: string;\n /** Whether to accept multiple files */\n multiple?: boolean;\n /** Additional CSS class */\n className?: string;\n /** Whether to hide the attach button */\n hideButton?: boolean;\n}\n\n/**\n * Formats file size to human-readable string\n * @param bytes - File size in bytes\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n};\n\n/**\n * Generates a unique ID for file tracking\n * @returns Unique string identifier\n */\nconst generateFileId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Reusable file attachment component\n *\n * Allows users to attach multiple files with preview and removal functionality.\n * No file size or type restrictions are applied on the client side.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * const [files, setFiles] = useState<AttachedFile[]>([]);\n *\n * <FileAttachment\n * files={files}\n * onFilesAdd={(newFiles) => {\n * const attachedFiles = newFiles.map(file => ({\n * file,\n * id: generateFileId()\n * }));\n * setFiles(prev => [...prev, ...attachedFiles]);\n * }}\n * onFileRemove={(id) => setFiles(prev => prev.filter(f => f.id !== id))}\n * multiple\n * />\n * ```\n */\nconst FileAttachment = ({\n files,\n onFilesAdd,\n onFileRemove,\n readOnly = false,\n buttonLabel = 'Anexar',\n multiple = true,\n className,\n hideButton = false,\n}: FileAttachmentProps) => {\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Handle file input change\n * @param event - Input change event\n */\n const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = event.target.files;\n if (selectedFiles && selectedFiles.length > 0) {\n const filesArray: File[] = Array.from(selectedFiles);\n onFilesAdd(filesArray);\n }\n // Reset input to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n };\n\n /**\n * Trigger file input click\n */\n const handleAttachClick = () => {\n fileInputRef.current?.click();\n };\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Hidden file input */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n onChange={handleFileChange}\n multiple={multiple}\n aria-label=\"Selecionar arquivos\"\n />\n\n {/* Attach button */}\n {!readOnly && !hideButton && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"small\"\n onClick={handleAttachClick}\n className=\"self-start flex items-center gap-2\"\n >\n <PaperclipIcon size={16} />\n {buttonLabel}\n </Button>\n )}\n\n {/* Attached files list */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((attachedFile) => (\n <div\n key={attachedFile.id}\n className=\"flex items-center gap-2 bg-background-50 px-3 py-2 rounded-lg border border-border-100\"\n >\n <FileTextIcon size={16} className=\"text-text-500 shrink-0\" />\n <Text className=\"text-sm text-text-700 truncate max-w-[200px]\">\n {attachedFile.file.name}\n </Text>\n <Text className=\"text-xs text-text-400 shrink-0\">\n {formatFileSize(attachedFile.file.size)}\n </Text>\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => onFileRemove(attachedFile.id)}\n className=\"text-text-400 hover:text-error-500 transition-colors shrink-0\"\n aria-label={`Remover ${attachedFile.file.name}`}\n >\n <XIcon size={14} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n\nexport { generateFileId, formatFileSize };\nexport default FileAttachment;\n"],"mappings":";;;;;;;;;;;AAAA,SAAgB,cAAc;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AA2HhB,cAWE,YAXF;AAjFN,IAAM,iBAAiB,CAAC,UAA0B;AAChD,MAAI,QAAQ,KAAM,QAAO,GAAG,KAAK;AACjC,MAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAC5D,MAAI,QAAQ,OAAO,OAAO;AACxB,WAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC;AAC9C,SAAO,IAAI,SAAS,OAAO,OAAO,OAAO,QAAQ,CAAC,CAAC;AACrD;AAMA,IAAM,iBAAiB,MAAc;AACnC,SAAO,OAAO,WAAW;AAC3B;AA6BA,IAAM,iBAAiB,CAAC;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX;AAAA,EACA,aAAa;AACf,MAA2B;AACzB,QAAM,eAAe,OAAyB,IAAI;AAMlD,QAAM,mBAAmB,CAAC,UAA+C;AACvE,UAAM,gBAAgB,MAAM,OAAO;AACnC,QAAI,iBAAiB,cAAc,SAAS,GAAG;AAC7C,YAAM,aAAqB,MAAM,KAAK,aAAa;AACnD,iBAAW,UAAU;AAAA,IACvB;AAEA,QAAI,aAAa,SAAS;AACxB,mBAAa,QAAQ,QAAQ;AAAA,IAC/B;AAAA,EACF;AAKA,QAAM,oBAAoB,MAAM;AAC9B,iBAAa,SAAS,MAAM;AAAA,EAC9B;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,uBAAuB,SAAS,GAEjD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,KAAK;AAAA,QACL,WAAU;AAAA,QACV,UAAU;AAAA,QACV;AAAA,QACA,cAAW;AAAA;AAAA,IACb;AAAA,IAGC,CAAC,YAAY,CAAC,cACb;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QAEV;AAAA,8BAAC,iBAAc,MAAM,IAAI;AAAA,UACxB;AAAA;AAAA;AAAA,IACH;AAAA,IAID,MAAM,SAAS,KACd,oBAAC,SAAI,WAAU,wBACZ,gBAAM,IAAI,CAAC,iBACV;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,8BAAC,gBAAa,MAAM,IAAI,WAAU,0BAAyB;AAAA,UAC3D,oBAAC,gBAAK,WAAU,gDACb,uBAAa,KAAK,MACrB;AAAA,UACA,oBAAC,gBAAK,WAAU,kCACb,yBAAe,aAAa,KAAK,IAAI,GACxC;AAAA,UACC,CAAC,YACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,aAAa,aAAa,EAAE;AAAA,cAC3C,WAAU;AAAA,cACV,cAAY,WAAW,aAAa,KAAK,IAAI;AAAA,cAE7C,8BAAC,SAAM,MAAM,IAAI;AAAA;AAAA,UACnB;AAAA;AAAA;AAAA,MAlBG,aAAa;AAAA,IAoBpB,CACD,GACH;AAAA,KAEJ;AAEJ;AAGA,IAAO,yBAAQ;","names":[]}