برنامه نویسی

نحوه ایجاد یک ویجت آپلود فایل جذاب با React/Next.js و Tailwind CSS

در چشم انداز وب مدرن، آپلود فایل یک ویژگی اساسی است که بسیاری از برنامه ها به آن نیاز دارند. خواه یک عکس نمایه در یک پلتفرم رسانه اجتماعی، یک پیوست در یک ایمیل، یا آپلود سند برای یک برنامه ذخیره سازی ابری باشد، مدیریت صحیح آپلود فایل برای یک تجربه کاربری عالی بسیار مهم است. با ظهور کتابخانه های جاوا اسکریپت مدرن و چارچوب های CSS، ایجاد چنین ویژگی هایی کارآمدتر از همیشه شده است. در این مقاله، می‌خواهیم قدرت React را بررسی کنیم، کتابخانه‌ای محبوب جاوا اسکریپت برای ساخت رابط‌های کاربری، جفت‌شده با Tailwind CSS، فریم‌ورک CSS اولین ابزار، و پسوند آن Daisy UI.

React با معماری مبتنی بر کامپوننت، امکان مقیاس بندی آسان و قابلیت استفاده مجدد کد را فراهم می کند. Tailwind CSS کلاس‌های کاربردی سطح پایینی را ارائه می‌دهد تا به شما امکان می‌دهد بدون ترک HTML، طرح‌های سفارشی بسازید. افزودن Daisy UI به ترکیب، جعبه ابزار ما را بیشتر تقویت می کند. به عنوان یک افزونه برای Tailwind CSS، Daisy UI ایجاد رابط های زیبا را در یک لحظه آسان می کند. این سبک وزن، آسان برای استفاده، و سرعت بخشیدن به روند توسعه قابل توجهی است.

با استفاده از نقاط قوت React، Tailwind CSS و Daisy UI، می‌توانیم یک آپلودکننده فایل کاربرپسند و زیبایی‌شناختی ایجاد کنیم. لطفاً با خیال راحت مخزن را در پایین صفحه بررسی یا شبیه سازی کنید. همچنین یک لینک به نسخه ی نمایشی نیز وجود دارد.

خوب به اندازه کافی غرولند، به کد!

جزء آپلود کننده فایل

'use client'
import React, { useState, ChangeEvent, useRef } from 'react';
import { FaCheck, FaTimes } from 'react-icons/fa';

interface FileUploaderProps {
    acceptedFileTypes?: string[] | null;
    url: string;
    maxFileSize?: number;
    allowMultiple?: boolean;
    label?: string;
    labelAlt?: string;
}

export default function FileUploader(props: FileUploaderProps) {
    const {
        acceptedFileTypes,
        url, maxFileSize = 5,
        allowMultiple = false,
        label = "",
        labelAlt = ""
    } = props;

    const MAX_FILE_BYTES = maxFileSize * 1024 * 1024; // MB to bytes

    // Change the state structure to handle multiple file progress and status
    const [fileProgress, setFileProgress] = useState<{ [key: string]: number }>({});
    const [fileStatus, setFileStatus] = useState<{ [key: string]: string }>({});
    const [uploadError, setUploadError] = useState<string | null>(null);
    const [uploadSuccess, setUploadSuccess] = useState<boolean>(false);

    const isError = Object.values(fileStatus).some(status => status !== 'Uploaded');

    // Create a ref for the file input
    const fileInputRef = useRef<HTMLInputElement>(null);

    const resetUploader = () => {
        setFileProgress({});
        setFileStatus({});
        setUploadError(null);
        setUploadSuccess(false);
        if (fileInputRef.current) {
            fileInputRef.current.value = "";
        }
    };

    const fileSelectedHandler = (event: ChangeEvent<HTMLInputElement>) => {
        setUploadError(null); // reset the upload error when a new file is selected
        if (event.target.files) {
            const files = Array.from(event.target.files);
            let isValid = true; // Flag to check if all files are valid
            let fileErrors: { [key: string]: string } = {};

            for (const file of files) {
                if (file.size > MAX_FILE_BYTES) {
                    fileErrors[file.name] = `File size cannot exceed ${maxFileSize} MB`;
                    isValid = false;
                }
                if (acceptedFileTypes && !acceptedFileTypes.includes(file.type)) {
                    fileErrors[file.name] = "File type not accepted. Accepted types: " + acceptedFileTypes.join(', ');
                    isValid = false;
                }
            }

            if (!isValid) {
                setFileStatus(fileErrors);
            } else {
                files.forEach(file => {
                    setFileProgress(prev => ({ ...prev, [file.name]: 0 }));
                    fileUploadHandler(file);
                });
            }
        }
    };

    const fileUploadHandler = (file: File) => {
        const formData = new FormData();
        formData.append("uploads", file);

        const xhr = new XMLHttpRequest();
        xhr.open("POST", url, true);

        xhr.upload.addEventListener("progress", event => {
            if (event.lengthComputable) {
                const progress = Math.round((event.loaded / event.total) * 100);
                setFileProgress(prev => ({ ...prev, [file.name]: progress }));
            }
        });

        xhr.addEventListener("readystatechange", () => {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    setFileStatus(prev => ({ ...prev, [file.name]: 'Uploaded' }));
                    setUploadSuccess(true);
                } else {
                    setFileStatus(prev => ({ ...prev, [file.name]: "An error occurred while uploading the file. Server response: " + xhr.statusText }));
                }
            }
        });

        xhr.send(formData);
    };

    return (
        <div className="flex flex-col gap-4 w-full h-60 md:h-48">
            {
                uploadSuccess
                    ?
                    <div className="flex flex-col gap-2">
                        {
                            isError ? <span className="text-xs text-red-500">Upload completed, but with errors.</span> : <></>
                        }
                        <div className="btn-group w-full">
                            <span className="btn btn-success w-1/2">Success!</span>
                            <button
                                className="btn w-1/2"
                                onClick={resetUploader}
                            >Upload Another</button>
                        </div>
                    </div>
                    :
                    <div className="form-control w-full">
                        <label className="label">
                            <span className="label-text">{label}</span>
                            <span className="label-text-alt">{labelAlt}</span>
                        </label>
                        <input
                            type="file"
                            className="file-input file-input-bordered file-input-primary w-full"
                            onChange={fileSelectedHandler}
                            accept={acceptedFileTypes ? acceptedFileTypes.join(',') : undefined}
                            ref={fileInputRef}
                            multiple={allowMultiple} // Added the 'multiple' attribute conditionally
                        />
                        <label className="label">
                            <span className="label-text-alt text-red-500">{uploadError}</span>
                        </label>
                    </div>
            }

            <div className="overflow-x-auto flex gap-2 flex-col-reverse">
                {Object.entries(fileProgress).map(([fileName, progress]) => (
                    <div key={fileName} className="text-xs flex flex-col gap-1">
                        <p>{fileName}</p>
                        <div className="flex items-center gap-2">
                            <progress
                                className="progress progress-primary w-full"
                                value={progress}
                                max="100"
                            />
                            {progress === 100 &&
                                <>
                                    {
                                        fileStatus[fileName] === 'Uploaded'
                                            ?
                                            <FaCheck className="text-xl text-green-500 mr-4" />
                                            :
                                            <FaTimes className="text-xl text-red-500 mr-4" />
                                    }
                                </>
                            }
                        </div>
                        <p className="text-red-500">{fileStatus[fileName] !== 'Uploaded' ? fileStatus[fileName] : ''}</p>
                    </div>
                ))}
            </div>
        </div>
    );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این جزء دارای چندین ویژگی است:

  • acceptedFileTypes = آرایه‌ای از رشته‌های نوع محتوا (پیش‌فرض برای همه انواع)
  • url = نقطه پایانی برای ارسال فایل
  • maxFileSize = حداکثر حجم فایل در مگابایت
  • allowMultiple = اجازه آپلود چند فایل (پیش‌فرض به نادرست)
  • label = برچسب کنترل فرم دست چپ
  • labelAlt = برچسب کنترل فرم خاموش دست راست

همچنین نه اینکه من از روتر مدرن برنامه Next.js 13 برای این نسخه نمایشی استفاده می کنم. بنابراین 'use client' دستورالعمل مورد نیاز است تا این یک جزء مشتری باشد، نه یک جزء سرور.

همه اش را بگذار کنار هم

در اینجا اصلی است page.tsx:

import FileUploader from "@/components/FileUploader"

const url = "/api/upload";

interface ContainerProps {
  children: React.ReactNode
}

const Container = ({ children }: ContainerProps) => (
  <div className="flex flex-col items-center justify-between gap-4 min-h-60 bg-zinc-800 w-full max-w-2xl py-10 px-4 rounded-xl h-fit">
    {children}
  </div>
)

export default function Home() {
  return (
    <main className="min-h-screen flex-col py-20 px-4 md:px-32 bg-zinc-900 text-white grid grid-cols-1 gap-8 lg:grid-cols-2 2xl:grid-cols-3">
      <Container>
        <h1 className="text-2xl font-bold">File Uploader</h1>
        <FileUploader
          url={url}
          acceptedFileTypes={[
            "image/png",
            "image/jpeg",
          ]}
          maxFileSize={100}
          label="Max File Size: 1MB"
          labelAlt="Accepted File Types: png, jpeg"
        />
      </Container>
      <Container>
        <h1 className="text-2xl font-bold">File Uploader</h1>
        <FileUploader
          url={url}
          acceptedFileTypes={[
            "image/png",
            "image/jpeg",
          ]}
          allowMultiple={true}
          maxFileSize={100}
          label="Max File Size: 100MB (multiple)"
          labelAlt="Accepted File Types: png, jpeg"
        />
      </Container>
      <Container>
        <h1 className="text-2xl font-bold">File Uploader</h1>
        <FileUploader
          url={'https://example.com'}
          acceptedFileTypes={[
            "image/png",
            "image/jpeg",
          ]}
          allowMultiple={true}
          maxFileSize={100}
          label="Max File Size: 100MB (non-existent endpoint)"
          labelAlt="Accepted File Types: png, jpeg"
        />
      </Container>

    </main >
  )
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خودشه! بدون نیاز به وابستگی های اضافی، این راه حل سبک، کاربردی، زیبا است و یک UX عالی ایجاد می کند. به سلامتی!


توجه: API سایت آزمایشی همه فایل‌هایی را که آپلود می‌شوند دور می‌اندازد. API همچنین به صورت تصادفی کد 500 را برای نمایش مدیریت خطا برمی گرداند.

سایت دمو | GitHub Repo


از اینکه وقت گذاشتید و مقاله من را خواندید متشکرم و امیدوارم برای شما مفید بوده باشد (یا حداقل، کمی سرگرم کننده). برای اطلاعات بیشتر درباره توسعه دهنده وب، مدیریت سیستم ها و محاسبات ابری، لطفاً وبلاگ Designly را بخوانید. همچنین، لطفا نظرات خود را بنویسید! من دوست دارم نظرات خوانندگانم را بشنوم.

من از هاستینگر برای میزبانی وب سایت های مشتریانم استفاده می کنم. شما می توانید یک حساب تجاری دریافت کنید که می تواند میزبان 100 وب سایت با قیمت 3.99 دلار در ماه باشد که می توانید تا 48 ماه آن را قفل کنید! این بهترین معامله در شهر است. خدمات شامل میزبانی PHP (با پسوند)، MySQL، WordPress و خدمات ایمیل است.

به دنبال توسعه دهنده وب هستید؟ من برای استخدام در دسترس هستم! برای استعلام لطفا فرم تماس را پر کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا