自定义☝️个文件上传组件

项目中为了简便直接使用了react-dropzone插件,但是遇到了点击上传后本地窗口怎么也关不掉这个bug,且自己也没找到原因(有了解原因的小伙伴欢迎),所以自己尝试完成一个可以点击/拖拽上传文件的组件:

项目技术栈:remix/tailwind

1、我把拖拽部分拆出来写了,方便复用或者额外操作,大家也可以与上传组件写一起: 

import React from "react";
import type { DragEvent, FC } from "react";

interface DraggerProps {
  onFile: (file: File) => void;//对文件的操作
  children?: React.ReactNode;//自定义样式
}

export const Dragger: FC<DraggerProps> = (props) => {
  const { onFile, children } = props;
  const handleDrop = (e: DragEvent<HTMLElement>) => {
    e.preventDefault();
    onFile(e.dataTransfer.files[0]);
  };
  return (
    <div
      className="flex h-full w-full cursor-pointer items-center justify-center"
      onDrop={handleDrop}
    >
      {children}
    </div>
  );
};

2、然后就是自定义的文件上传组件,可根据自身需求对传入的参数/上传的文件进行修改:

import {
  FileArchive,
  FileJson,
  FileText,
  UploadCloud,
  XCircle,
} from "lucide-react";//一个icon插件,根据需求自定义
import type { ChangeEvent, FC, ReactNode } from "react";
import { useRef } from "react";
import { cn } from "~/lib/utils";//use
import Dragger from "../Dragger";
import { Button } from "../ui/button";

//文件大小限制
export const readableBytes = (bytes: number, decimals?: number) => {
  if (bytes == 0) return "0 Bytes";
  const k = 1024,
    dm = decimals || 2,
    sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
    i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
};

export interface UploadProps {
  name?: string;//文件名
  accept?: string;//接受的文件类型
  maxSize?: number;//文件最大体积
  tip?: string;//提示信息
  file?: File;
  preview?: ReactNode;//上传的文件预览
  onFileChange?: (file?: File) => void;
}
export const FileUpload: FC<UploadProps> = (props) => {
  const { accept, maxSize, tip, file, preview, onFileChange } = props;
  const fileInput = useRef<HTMLInputElement>(null);
  const handleClick = () => {
    if (fileInput.current) {
      fileInput.current.click();
    }
  };
  const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;
    if (!files?.length) {
      return;
    }
    onFileChange?.(files[0]);
    if (fileInput.current) {
      fileInput.current.value = "";
    }
  };

  return (
    <div
      className={cn(
        "flex h-64 w-full items-center justify-center rounded-2xl bg-accent-background text-muted transition-colors hover:bg-item dark:bg-dialog-foreground dark:hover:bg-item"
      )}
      onClick={handleClick}
    >
      {file ? (
        <div className="flex h-full w-full flex-col items-center p-4">
          <div className="dark:border-dialog-background flex w-full items-center rounded-xl border border-muted-foreground bg-background px-6 py-3 dark:bg-[#302F34]">
            <div className="mr-3 overflow-hidden rounded-lg">
              {preview ? (
                preview
              ) : file.type.startsWith("image") ? (
                <img
                  src={URL.createObjectURL(file)}
                  alt="upload"
                  className="h-14 w-14"
                />
              ) : file.type.endsWith("json") ? (
                <FileJson className="h-14 w-14" />
              ) : file.type.endsWith("zip") ? (
                <FileArchive className="h-14 w-14" />
              ) : file.type.endsWith("txt") ? (
                <FileText className="h-14 w-14" />
              ) : (
                <span className="flex h-14 w-14 items-center justify-center rounded-lg bg-muted text-sm text-background">
                  .{file.type.split("/")[file.type.split("/").length - 1]}
                </span>
              )}
            </div>

            <div className="flex flex-1 flex-col space-y-1">
              <span className="font-bold">{file.name}</span>
              <span
                className={cn("text-sm text-muted-background", {
                  "text-destructive": maxSize && file.size > maxSize,
                })}
              >
                {readableBytes(file.size)}
              </span>
            </div>
            <Button
              size="sm"
              variant="ghost"
              onClick={() => onFileChange?.(undefined)}
              className="hover:bg-transparent"
            >
              <XCircle className="delay-50 h-6 w-6 text-foreground hover:text-muted dark:text-secondary-foreground dark:hover:text-muted" />
            </Button>
          </div>
        </div>
      ) : (
        <Dragger onFile={(file: File) => onFileChange?.(file)}>
          <div className="inter flex flex-col items-center justify-center space-y-2 p-6 text-center text-sm text-search-icon">
            <div className="dark:bg-dialog-background flex h-10 w-10 items-center justify-center rounded-xl border border-muted-foreground bg-background text-search-placeholder dark:border-transparent dark:text-muted-background">
              <UploadCloud className="h-5 w-5" />
            </div>
            <p>
              <span className="font-bold">Click to upload</span> or drag and
              drop
            </p>
            <p>
              {`${
                tip || ".svg, .png, .jpg, .gif, .txt, .pdf, .json, .mp4..."
              } ${maxSize && `(up to ${readableBytes(maxSize)})`}`}
            </p>
          </div>
        </Dragger>
      )}
      <input
        className="hidden"
        ref={fileInput}
        onChange={handleFileChange}
        type="file"
        accept={accept}
        disabled={!!file}
      />
    </div>
  );
};
FileUpload.defaultProps = {
  name: "file",
};
export default FileUpload;
🥳🥳🥳至此一个简单不需要任何插件的自定义文件上传组件就完成啦~~其中代码逻辑还有许多不足,欢迎大家给我提出宝贵建议噢~~💕💕
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值