From e1be590f1db7de22047ca1be14c8d99eeef36ebe Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 23 Jul 2023 12:57:03 +0800 Subject: [PATCH] Improve FileSelectBox key behavior Properly highlight button with current action. --- .../src/components/FileSelectBox/index.tsx | 186 ++++++++++++++---- .../src/components/AudioFilePreview/index.tsx | 4 +- .../components/BinaryFilePreview/index.tsx | 4 +- .../src/components/ImageFilePreview/index.tsx | 4 +- .../src/components/TextFilePreview/index.tsx | 4 +- .../src/components/VideoFilePreview/index.tsx | 4 +- 6 files changed, 154 insertions(+), 52 deletions(-) diff --git a/categories/blob/react/src/components/FileSelectBox/index.tsx b/categories/blob/react/src/components/FileSelectBox/index.tsx index e284d30..6fb57b1 100644 --- a/categories/blob/react/src/components/FileSelectBox/index.tsx +++ b/categories/blob/react/src/components/FileSelectBox/index.tsx @@ -2,10 +2,22 @@ import * as React from 'react'; import { useClientSide, useFallbackId, useProxyInput } from '@modal-sh/react-utils'; import clsx from 'clsx'; -export interface CommonPreviewProps = Partial> { +export interface CommonPreviewComponentProps = Partial> { + /** + * The file to preview. + */ file?: F; + /** + * Is the component disabled? + */ disabled?: boolean; + /** + * Should the component be enhanced? + */ enhanced?: boolean; + /** + * Should the component use minimal space? + */ mini?: boolean; } @@ -13,7 +25,7 @@ export type FileSelectBoxDerivedElement = HTMLInputElement; export interface FileSelectBoxProps< F extends Partial = Partial, - P extends CommonPreviewProps = CommonPreviewProps + P extends CommonPreviewComponentProps = CommonPreviewComponentProps > extends Omit, 'size' | 'type' | 'label' | 'list'> { /** * Should the component display a border? @@ -39,46 +51,67 @@ export interface FileSelectBoxProps< * Is the label hidden? */ hiddenLabel?: boolean, + /** + * Preview component for the selected file(s). + */ previewComponent?: React.ElementType

, } -export const FileSelectBoxDefaultPreviewComponent: React.FC = ({ +export const FileSelectBoxDefaultPreviewComponent = React.forwardRef< + HTMLDivElement, + CommonPreviewComponentProps +>(({ file, mini, -}) => ( - <> + enhanced, + disabled, +}, forwardedRef) => ( +

{file?.name ?? ( - - File - + + File + )}
{!mini && ( - <> - {typeof file?.type === 'string' && ( -
{file?.type}
- )} - {typeof file?.size === 'number' && ( -
- {new Intl.NumberFormat(undefined, { - style: 'unit', - unit: 'byte', - unitDisplay: 'long', - }).format(file.size ?? 0)} -
- )} - {typeof file?.lastModified === 'number' && ( -
- -
- )} - + <> + {typeof file?.type === 'string' && ( +
{file?.type}
+ )} + {typeof file?.size === 'number' && ( +
+ {new Intl.NumberFormat(undefined, { + style: 'unit', + unit: 'kilobyte', + unitDisplay: 'long', + }).format(file.size ?? 0)} +
+ )} + {typeof file?.lastModified === 'number' && ( +
+ +
+ )} + )} - -); +
+)); export const FileSelectBox = React.forwardRef(( { @@ -104,6 +137,8 @@ export const FileSelectBox = React.forwardRef(); const [lastUpdated, setLastUpdated] = React.useState(); const clearFileListRef = React.useRef(false); + const [deleteKeyPressed, setDeleteKeyPressed] = React.useState(false); + const [aboutToSelect, setAboutToSelect] = React.useState(false); const { defaultRef, handleChange: doSetFileList } = useProxyInput< React.ChangeEvent | React.MouseEvent @@ -113,7 +148,7 @@ export const FileSelectBox = React.forwardRef({ forwardedRef, valueSetterFn: (e) => { - if (e.type === 'click') { + if (e.type === 'mouseup') { // delete const fileInput = defaultRef.current as FileSelectBoxDerivedElement; clearFileListRef.current = true; @@ -143,7 +178,6 @@ export const FileSelectBox = React.forwardRef = (e) => { + const { code } = e; + if (code === 'Backspace' || code === 'Delete') { + setDeleteKeyPressed(true); + } else if (code === 'Enter' || code === 'Space' || code === 'Return') { + setAboutToSelect(true); + } + }; + const handleKeyUp: React.KeyboardEventHandler = (e) => { const { code } = e; if (code === 'Backspace' || code === 'Delete') { doSetFileList(e); + setDeleteKeyPressed(false); + } else if (code === 'Enter' || code === 'Space' || code === 'Return') { + setAboutToSelect(false); } }; + const handleReselectMouseDown: React.MouseEventHandler< + HTMLLabelElement + > = React.useCallback(() => { + setAboutToSelect(true); + setTimeout(() => { + const fileInput = defaultRef.current as FileSelectBoxDerivedElement; + fileInput.focus(); + }); + }, [defaultRef]); + + const handleDeleteMouseDown: React.MouseEventHandler< + HTMLButtonElement + > = React.useCallback(() => { + setDeleteKeyPressed(true); + setTimeout(() => { + const fileInput = defaultRef.current as FileSelectBoxDerivedElement; + fileInput.focus(); + }); + }, [defaultRef]); + + React.useEffect(() => { + const handleLabelMouseUp = () => { + setAboutToSelect(false); + setDeleteKeyPressed(false); + }; + + window.addEventListener('mouseup', handleLabelMouseUp, { capture: true }); + return () => { + window.removeEventListener('mouseup', handleLabelMouseUp, { capture: true }); + }; + }, []); + return (
{placeholder} @@ -218,8 +297,9 @@ export const FileSelectBox = React.forwardRef