Skip to content

Commit

Permalink
fix(FileUploaderFile): wrap component in forwardRef (#3238)
Browse files Browse the repository at this point in the history
  • Loading branch information
Funkicide committed Aug 11, 2023
1 parent 3631912 commit 8dd42d6
Showing 1 changed file with 143 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';

import { forwardRefAndName } from '../../../lib/forwardRefAndName';
import { FileUploaderAttachedFile } from '../fileUtils';
import { formatBytes } from '../../../lib/utils';
import { TextWidthHelper } from '../../../internal/TextWidthHelper/TextWidthHelper';
Expand Down Expand Up @@ -64,155 +65,157 @@ export const FileUploaderFileDataTids = {
fileIcon: 'FileUploader__fileIcon',
} as const;

export const FileUploaderFile = (props: FileUploaderFileProps) => {
const { file, showSize, error, multiple, size } = props;
const { id, originalFile, status, validationResult } = file;
const { name, size: fileSize } = originalFile;
export const FileUploaderFile = forwardRefAndName<HTMLDivElement, FileUploaderFileProps>(
'FileUploaderFile',
(props, ref) => {
const { file, showSize, error, multiple, size } = props;
const { id, originalFile, status, validationResult } = file;
const { name, size: fileSize } = originalFile;

const [hovered, setHovered] = useState<boolean>(false);
const [focusedByTab, setFocusedByTab] = useState(false);
const [truncatedFileName, setTruncatedFileName] = useState<Nullable<string>>(null);
const [hovered, setHovered] = useState<boolean>(false);
const [focusedByTab, setFocusedByTab] = useState(false);
const [truncatedFileName, setTruncatedFileName] = useState<Nullable<string>>(null);

const textHelperRef = useRef<TextWidthHelper>(null);
const fileNameElementRef = useRef<HTMLSpanElement>(null);
const textHelperRef = useRef<TextWidthHelper>(null);
const fileNameElementRef = useRef<HTMLSpanElement>(null);

const { removeFile, setIsMinLengthReached, isMinLengthReached } = useContext(FileUploaderControlContext);
const theme = useContext(ThemeContext);
const { removeFile, setIsMinLengthReached, isMinLengthReached } = useContext(FileUploaderControlContext);
const theme = useContext(ThemeContext);

const formattedSize = useMemo(() => formatBytes(fileSize, 1), [fileSize]);
const formattedSize = useMemo(() => formatBytes(fileSize, 1), [fileSize]);

useEffect(() => {
if (setIsMinLengthReached) {
const truncatedName = calcTruncatedName(textHelperRef, fileNameElementRef, name);
useEffect(() => {
if (setIsMinLengthReached) {
const truncatedName = calcTruncatedName(textHelperRef, fileNameElementRef, name);

setIsMinLengthReached((truncatedName?.length ?? 0) <= MIN_CHARS_LENGTH);
}
}, [name, isMinLengthReached]);

useEffect(() => {
const truncatedName = calcTruncatedName(textHelperRef, fileNameElementRef, name);

setTruncatedFileName(truncatedName);
});

const removeUploadFile = useCallback(() => {
removeFile(id);
}, [removeFile, id]);

const handleRemove = useCallback(
(event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
event.stopPropagation();
removeUploadFile();
},
[removeUploadFile],
);

const { isValid, message } = validationResult;

const isInvalid = error || !isValid;

const sizeIconClass = useFileUploaderSize(size, {
small: jsStyles.iconSmall(),
medium: jsStyles.iconMedium(),
large: jsStyles.iconLarge(),
});

const renderTooltipContent = useCallback((): ReactNode => {
return !isValid && !error && message ? message : null;
}, [isValid, error, message]);

const sizeContentClass = useFileUploaderSize(size, {
small: jsStyles.contentSmall(theme),
medium: jsStyles.contentMedium(theme),
large: jsStyles.contentLarge(theme),
});

const contentClassNames = cx(jsStyles.content(), {
[sizeContentClass]: true,
[jsStyles.error(theme)]: isInvalid,
});

const handleMouseEnter = useCallback(() => {
setHovered(true);
}, []);

const handleMouseLeave = useCallback(() => {
setHovered(false);
}, []);

const handleFocus = useCallback(() => {
// focus event fires before keyDown eventlistener
// so we should check tabPressed in async way
requestAnimationFrame(() => {
if (keyListener.isTabPressed) {
setFocusedByTab(true);
setIsMinLengthReached((truncatedName?.length ?? 0) <= MIN_CHARS_LENGTH);
}
}, [name, isMinLengthReached]);

useEffect(() => {
const truncatedName = calcTruncatedName(textHelperRef, fileNameElementRef, name);

setTruncatedFileName(truncatedName);
});
}, []);

const handleBlur = useCallback(() => {
setFocusedByTab(false);
}, []);
const removeUploadFile = useCallback(() => {
removeFile(id);
}, [removeFile, id]);

const handleIconKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLElement>) => {
if (isKeyEnter(e)) {
const handleRemove = useCallback(
(event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
event.stopPropagation();
removeUploadFile();
}
},
[removeUploadFile],
);

const iconClassNames = cx(jsStyles.icon(theme), {
[jsStyles.focusedIcon(theme)]: focusedByTab,
[sizeIconClass]: true,
[jsStyles.iconMultiple()]: multiple,
});

const isTruncated = truncatedFileName !== name;

return (
<div
data-tid={FileUploaderFileDataTids.file}
className={jsStyles.root()}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Tooltip data-tid={FileUploaderFileDataTids.fileTooltip} pos="right middle" render={renderTooltipContent}>
<div className={contentClassNames}>
<TextWidthHelper ref={textHelperRef} text={name} />
<Hint maxWidth={'100%'} text={isTruncated ? name : null}>
<span data-tid={FileUploaderFileDataTids.fileName} ref={fileNameElementRef} className={jsStyles.name()}>
{truncatedFileName}
</span>
</Hint>
{!!showSize && formattedSize && (
<span data-tid={FileUploaderFileDataTids.fileSize} className={jsStyles.size()}>
{formattedSize}
</span>
)}
<div
className={iconClassNames}
data-tid={FileUploaderFileDataTids.fileIcon}
tabIndex={0}
onClick={handleRemove}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleIconKeyDown}
>
<FileUploaderFileStatusIcon
status={status}
hovered={hovered}
focusedByTab={focusedByTab}
isInvalid={isInvalid}
size={size}
/>
</div>
</div>
</Tooltip>
</div>
);
};
},
[removeUploadFile],
);

const { isValid, message } = validationResult;

FileUploaderFile.displayName = 'FileUploaderFile';
const isInvalid = error || !isValid;

const sizeIconClass = useFileUploaderSize(size, {
small: jsStyles.iconSmall(),
medium: jsStyles.iconMedium(),
large: jsStyles.iconLarge(),
});

const renderTooltipContent = useCallback((): ReactNode => {
return !isValid && !error && message ? message : null;
}, [isValid, error, message]);

const sizeContentClass = useFileUploaderSize(size, {
small: jsStyles.contentSmall(theme),
medium: jsStyles.contentMedium(theme),
large: jsStyles.contentLarge(theme),
});

const contentClassNames = cx(jsStyles.content(), {
[sizeContentClass]: true,
[jsStyles.error(theme)]: isInvalid,
});

const handleMouseEnter = useCallback(() => {
setHovered(true);
}, []);

const handleMouseLeave = useCallback(() => {
setHovered(false);
}, []);

const handleFocus = useCallback(() => {
// focus event fires before keyDown eventlistener
// so we should check tabPressed in async way
requestAnimationFrame(() => {
if (keyListener.isTabPressed) {
setFocusedByTab(true);
}
});
}, []);

const handleBlur = useCallback(() => {
setFocusedByTab(false);
}, []);

const handleIconKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLElement>) => {
if (isKeyEnter(e)) {
removeUploadFile();
}
},
[removeUploadFile],
);

const iconClassNames = cx(jsStyles.icon(theme), {
[jsStyles.focusedIcon(theme)]: focusedByTab,
[sizeIconClass]: true,
[jsStyles.iconMultiple()]: multiple,
});

const isTruncated = truncatedFileName !== name;

return (
<div
data-tid={FileUploaderFileDataTids.file}
className={jsStyles.root()}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
ref={ref}
>
<Tooltip data-tid={FileUploaderFileDataTids.fileTooltip} pos="right middle" render={renderTooltipContent}>
<div className={contentClassNames}>
<TextWidthHelper ref={textHelperRef} text={name} />
<Hint maxWidth={'100%'} text={isTruncated ? name : null}>
<span data-tid={FileUploaderFileDataTids.fileName} ref={fileNameElementRef} className={jsStyles.name()}>
{truncatedFileName}
</span>
</Hint>
{!!showSize && formattedSize && (
<span data-tid={FileUploaderFileDataTids.fileSize} className={jsStyles.size()}>
{formattedSize}
</span>
)}
<div
className={iconClassNames}
data-tid={FileUploaderFileDataTids.fileIcon}
tabIndex={0}
onClick={handleRemove}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleIconKeyDown}
>
<FileUploaderFileStatusIcon
status={status}
hovered={hovered}
focusedByTab={focusedByTab}
isInvalid={isInvalid}
size={size}
/>
</div>
</div>
</Tooltip>
</div>
);
},
);

0 comments on commit 8dd42d6

Please sign in to comment.