useFileInput.tsx 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { useCallback, useRef, useState } from "react";
  2. interface UseFileInputParams {
  3. directory?: boolean;
  4. accept?: string;
  5. }
  6. /**
  7. * Return three things:
  8. *
  9. * - A function that can be called to trigger the showing of the select file /
  10. * directory dialog.
  11. *
  12. * - The list of properties that should be passed to a dummy `input` element
  13. * that needs to be created to anchor the select file dialog. This input HTML
  14. * element is not going to be visible, but it needs to be part of the DOM fro
  15. * the open trigger to have effect.
  16. *
  17. * - The list of files that the user selected. This will be a list even if the
  18. * user selected directories - in that case, it will be the recursive list of
  19. * files within this directory.
  20. *
  21. * @param param0
  22. *
  23. * - If {@link directory} is true, the file open dialog will ask the user to
  24. * select directories. Otherwise it'll ask the user to select files.
  25. *
  26. * - If {@link accept} is specified, it'll restrict the type of files that the
  27. * user can select by setting the "accept" attribute of the underlying HTML
  28. * input element we use to surface the file selector dialog. For value of
  29. * accept can be an extension or a MIME type (See
  30. * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept).
  31. */
  32. export default function useFileInput({
  33. directory,
  34. accept,
  35. }: UseFileInputParams) {
  36. const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
  37. const inputRef = useRef<HTMLInputElement>();
  38. const openSelectorDialog = useCallback(() => {
  39. if (inputRef.current) {
  40. inputRef.current.value = null;
  41. inputRef.current.click();
  42. }
  43. }, []);
  44. const handleChange: React.ChangeEventHandler<HTMLInputElement> = async (
  45. event,
  46. ) => {
  47. if (!!event.target && !!event.target.files) {
  48. setSelectedFiles([...event.target.files]);
  49. }
  50. };
  51. // [Note: webkitRelativePath]
  52. //
  53. // If the webkitdirectory attribute of an <input> HTML element is set then
  54. // the File objects that we get will have `webkitRelativePath` property
  55. // containing the relative path to the selected directory.
  56. //
  57. // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
  58. const directoryOpts = directory
  59. ? { directory: "", webkitdirectory: "" }
  60. : {};
  61. const getInputProps = useCallback(
  62. () => ({
  63. type: "file",
  64. multiple: true,
  65. style: { display: "none" },
  66. ...directoryOpts,
  67. ref: inputRef,
  68. onChange: handleChange,
  69. ...(accept ? { accept } : {}),
  70. }),
  71. [],
  72. );
  73. return {
  74. getInputProps,
  75. open: openSelectorDialog,
  76. selectedFiles: selectedFiles,
  77. };
  78. }