import React, { useMemo, useState } from 'react'
import Block from '../../Primitives/Block/Block'
import { ButtonV2, ButtonV2Size, ButtonV2Type } from '../../ButtonV2'
import PrimitiveButton from '../../Primitives/PrimitiveButton/PrimitiveButton'
import { CaretDownIcon, CaretUpIcon, SwapIcon } from '@phosphor-icons/react'
import Text from '../../Text/Text'
import type { UploadV2TokensType } from './UploadV2.tokens.types'
import {
TagV2,
TagV2Color,
TagV2Size,
TagV2SubType,
TagV2Type,
} from '../../TagV2'
import { XIcon } from '@phosphor-icons/react'
import TooltipV2 from '../../TooltipV2/TooltipV2'
import PopoverV2 from '../../PopoverV2/PopoverV2'
import { PopoverV2Align, PopoverV2Side } from '../../PopoverV2/popoverV2.types'
import {
getFileId,
getValidationMessage,
normalizeUploadErrorReason,
truncateFileNameForTag,
} from './utils'
import { UploadDragState, UploadFileV2, UploadState } from './UploadV2.types'
import {
ProgressBarV2,
ProgressBarV2Appearance,
ProgressBarV2Size,
ProgressBarV2Variant,
} from '../../ProgressBarV2'
const UploadContainerV2 = ({
description,
descriptionId,
slot,
disabled,
onClick,
tokens,
files,
onFileRemove,
multiple,
state,
errorText = '',
errorId,
progressBarValue,
progressBarMaxWidth,
uploadHeaderText,
dragState,
maxSize,
maxFiles,
}: {
description: string
descriptionId?: string
slot: React.ReactNode
disabled: boolean
onClick: () => void
tokens: UploadV2TokensType
files: UploadFileV2[]
onFileRemove: (fileId: string) => void
multiple: boolean
state: UploadState
errorText: string
errorId?: string
progressBarValue: number
progressBarMaxWidth: string
uploadHeaderText: string
dragState: UploadDragState
maxSize?: number
maxFiles?: number
}) => {
const [isOverflowPopoverOpen, setIsOverflowPopoverOpen] = useState(false)
const isUploading = state === UploadState.UPLOADING
const isSuccess = state === UploadState.SUCCESS
const isInteractionBlocked = isUploading || isSuccess
const { uploadContainer } = tokens
const showEmptyDescription = files.length === 0 && !isUploading
const showSingleFileInfo = files.length > 0 && !multiple && !isUploading
const showBrowseButton = files.length === 0 && !isUploading
const showReplaceButton = files.length > 0 && !multiple && !isUploading
const showMultiFileTags = files.length > 0 && multiple && !isUploading
const validFileCount = files.filter((file) => file.isValid).length
const invalidFileCount = files.length - validFileCount
const showFilledMultiFileCopy = showMultiFileTags
const headerText = showFilledMultiFileCopy
? invalidFileCount > 0
? 'Files uploaded'
: 'Files successfully added'
: uploadHeaderText
const filledMultiFileDescription =
invalidFileCount > 0
? `${validFileCount} succeeded, ${invalidFileCount} failed`
: "We've successfully uploaded the following files"
const isDragEnter = dragState === UploadDragState.DRAG_ENTER
const isDragOver = dragState === UploadDragState.DRAG_OVER
const isDrop = dragState === UploadDragState.DROP
const isDragActive = isDragEnter || isDragOver || isDrop
const sortedFiles = useMemo(
() =>
[...files].sort((a, b) => {
if (!a.isValid && b.isValid) return -1
if (a.isValid && !b.isValid) return 1
return 0
}),
[files]
)
const visibleFiles = sortedFiles.slice(0, 4)
const overflowFiles = sortedFiles.slice(4)
const renderFileTag = (uploadFile: UploadFileV2, index: number) => {
const fileId = getFileId(uploadFile, index)
const fileColor =
state === UploadState.SUCCESS
? TagV2Color.SUCCESS
: uploadFile.isValid
? TagV2Color.NEUTRAL
: TagV2Color.ERROR
const tooltipContent = uploadFile.isValid
? uploadFile.file.name
: `${uploadFile.file.name} - ${getValidationMessage(
uploadFile.errorReason,
maxSize,
maxFiles
)}`
return (
}}
text={truncateFileNameForTag(uploadFile.file.name)}
size={TagV2Size.SM}
type={TagV2Type.SUBTLE}
subType={TagV2SubType.ROUNDED}
color={fileColor}
onClick={(e) => {
if (disabled || isInteractionBlocked) return
e.stopPropagation()
onFileRemove?.(fileId)
}}
/>
)
}
const renderOverflowFileRow = (uploadFile: UploadFileV2, index: number) => {
const fileId = getFileId(uploadFile, index)
return (
{uploadFile.file.name}
{!uploadFile.isValid && (
)}
{
if (disabled || isInteractionBlocked) return
e.stopPropagation()
onFileRemove?.(fileId)
}}
display="flex"
alignItems="center"
justifyContent="center"
backgroundColor="transparent"
border="none"
padding="0"
cursor={
disabled || isInteractionBlocked
? 'not-allowed'
: 'pointer'
}
>
)
}
return (
{slot && !showFilledMultiFileCopy && {slot}}
{headerText}
{(showEmptyDescription || showFilledMultiFileCopy) && (
{showFilledMultiFileCopy
? filledMultiFileDescription
: description}
)}
{showSingleFileInfo && (
{'Selected file: ' + files[0].file.name}
)}
{isUploading && (
{'Please wait while uploading'}
)}
{isUploading && (
)}
{showBrowseButton && !isInteractionBlocked && (
{
e.stopPropagation()
onClick()
}}
/>
)}
{showReplaceButton && !isInteractionBlocked && (
{
e.stopPropagation()
onClick()
}}
leftSlot={{ slot: }}
/>
)}
{/* for multiple files, show a list of files */}
{showMultiFileTags && (
{visibleFiles.map((uploadFile) =>
renderFileTag(uploadFile, files.indexOf(uploadFile))
)}
{overflowFiles.length > 0 && (
e.stopPropagation()}
display="flex"
backgroundColor="transparent"
border="none"
padding="0"
cursor="pointer"
width="fit-content"
>
) : (
),
}}
/>
}
minWidth={240}
maxWidth={340}
maxHeight={280}
showCloseButton={false}
asModal={true}
side={PopoverV2Side.BOTTOM}
align={PopoverV2Align.CENTER}
>
{overflowFiles.map((uploadFile) =>
renderOverflowFileRow(
uploadFile,
files.indexOf(uploadFile)
)
)}
)}
)}
{state === UploadState.ERROR && !errorText && !multiple && (
{(() => {
const invalidFiles = files.filter((f) => !f.isValid)
const hasOversized = invalidFiles.some(
(f) =>
normalizeUploadErrorReason(f.errorReason) ===
'oversized'
)
const hasMaxFiles = invalidFiles.some(
(f) =>
normalizeUploadErrorReason(f.errorReason) ===
'maxFiles'
)
const hasInvalidType = invalidFiles.some(
(f) =>
normalizeUploadErrorReason(f.errorReason) ===
'invalidType'
)
const errors: string[] = []
if (hasOversized)
errors.push(
getValidationMessage(
'oversized',
maxSize,
maxFiles
)
)
if (hasMaxFiles)
errors.push(
getValidationMessage(
'maxFiles',
maxSize,
maxFiles
)
)
if (hasInvalidType)
errors.push(
getValidationMessage(
'invalidType',
maxSize,
maxFiles
)
)
return errors.length > 0
? `${errors.join(', ')}`
: getValidationMessage(undefined, maxSize, maxFiles)
})()}
)}
{errorText && (
{errorText}
)}
)
}
export default UploadContainerV2