Maison  >  Article  >  interface Web  >  Implémentation du glisser-déposer pour organiser/trier les éléments avec React, Tailwind CSS et Dnd-kit

Implémentation du glisser-déposer pour organiser/trier les éléments avec React, Tailwind CSS et Dnd-kit

PHPz
PHPzoriginal
2024-07-25 08:23:12530parcourir

Implementing Drag and Drop to Arrange/Sort Items with React, Tailwind CSS, and Dnd-kit

Introduction

Vous êtes-vous déjà demandé comment des applications comme Trello ou Asana gèrent leurs interfaces intuitives par glisser-déposer ? Imaginez que vous ayez une application dans laquelle les utilisateurs doivent trier facilement leurs éléments. Sans une fonction glisser-déposer fluide, cette tâche peut devenir fastidieuse et frustrante. Dans cet article de blog, nous explorerons comment implémenter une fonctionnalité de glisser-déposer dynamique à l'aide de React, Tailwind CSS et Dnd-kit pour créer une expérience utilisateur transparente pour organiser et trier les éléments.

Problème du monde réel

Dans le monde réel, les applications nécessitent souvent que les utilisateurs réorganisent les éléments en fonction de la priorité, du statut ou d'autres critères. Par exemple, un utilisateur peut avoir besoin de réorganiser rapidement ses idées lors d'une séance de brainstorming. Sans une fonctionnalité de glisser-déposer efficace, ce processus pourrait impliquer des étapes fastidieuses telles que la modification manuelle de la position des éléments ou l'utilisation de boutons de déplacement vers le haut/vers le bas inefficaces. Notre objectif est de fournir une solution qui simplifie ce processus, le rendant plus intuitif et efficace pour les utilisateurs.

Cas d'utilisation

Considérons un cas d'utilisation d'un outil de brainstorming où les utilisateurs peuvent organiser leurs idées. Les utilisateurs doivent pouvoir :

  • Ajoutez de nouvelles idées à une liste.

  • Triez et hiérarchisez ces idées en les faisant glisser et en les déposant dans l'ordre souhaité.

  • Déplacez les idées entre différentes catégories (par exemple, nouvelles idées par rapport aux anciennes idées).

Pour y parvenir, nous allons créer une application React en utilisant Vite pour la configuration du projet, Tailwind CSS pour le style et Dnd-kit pour la fonctionnalité glisser-déposer. Cette configuration nous permettra de créer une interface conviviale qui améliore la productivité et l'expérience utilisateur.

Mise en place du projet

  • Initialiser le projet Vite

npm create vite@latest my-drag-drop-app --template réagir
cd mon-glisser-déposer-app
installation npm

  • Installer les dépendances requises :

npm install tailwindcss dnd-kit réagir-chaud-toast réagir-icônes

  • Configurer le CSS Tailwind :

npx tailwindcss init

  • Mettre à jour tailwind.config.js :
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  ],
  plugins: [],
}
  • Ajouter des directives Tailwind à index.css :
@tailwind base;
@tailwind components;
@tailwind utilities;

Implémentation du glisser-déposer

App.jsx

Le fichier App.jsx est le composant principal qui configure la présentation globale de l'application et gère l'état global.

Résumé

  • Composant principal pour gérer l'état global de l'application.

  • Utilise useState pour gérer les données et les mises à jour du projet.

  • Incorpore les composants Header et DragDropArrange pour l'interface utilisateur et les fonctionnalités.

  • Comprend le grille-pain de React-hot-toast pour les notifications.

Fonctions clés :

  • Gestion de l'état : Gère l'état des données du projet.

  • Gérer les nouvelles données : Fonction permettant d'ajouter de nouvelles données à l'état des données du projet.

  • Mise en page : Configure la mise en page, y compris l'en-tête, le contenu principal et les notifications.

import React, { useState } from 'react';
import { Toaster } from 'react-hot-toast';
import Header from './screens/Navigation/Header';
import DragDropArrange from './screens/DDA/DragDropArrange';
import projectDataJson from "./Data/data.json"

function App() {

  const [projectData, setProjectData] = useState(projectDataJson)
  function handleNewData(data){
    const tempData = projectData.newIdea;
    const maxNumber = (Math.random() * 100) * 1000;
    tempData.push({_id: maxNumber, idea: data});
    setProjectData({...data, newIdea: tempData})
  }

  return (
    <div className="h-auto overflow-auto">
      <div className='w-full h-auto overflow-auto fixed z-50'>
      <Header handleNewData={handleNewData}/>
      </div>
      <div className="h-auto overflow-auto my-16 flex items-center justify-center">
        <DragDropArrange projectData={projectData}/>
      </div>
      <Toaster
        toastOptions={{
          className: 'text-xs',
          duration: 3000,
        }}
      />
    </div>
  );
}

export default App;

En-tête.jsx

Le fichier Header.jsx sert de barre de navigation, fournissant un bouton pour ouvrir un formulaire permettant d'ajouter de nouveaux éléments.

Résumé :

  • Contient la navigation et un bouton pour ouvrir le formulaire de saisie d'élément.

  • Utilise useState pour gérer l'état de la visibilité du formulaire de saisie de l'élément.

  • Gère les interactions des utilisateurs pour l'ajout de nouveaux éléments.

Fonctions clés :

  • Bascule du formulaire d'élément : Gère la visibilité du formulaire de saisie d'élément.

  • Gérer les nouvelles données : Transmet les nouvelles données de l'élément au composant parent.

import React, { useState } from 'react';
import { PiNotepadFill } from "react-icons/pi";
import AddIdea from '../DDA/AddIdea';

const Header = ({handleNewData}) => {
  const [ideaTabOpen, setIdeaTabOpen] = useState(false)
  return (
    <>
      <nav className='w-full h-auto p-2 float-left overflow-auto bg-black flex items-center justify-center'>
        <div className='w-2/5 mdw-3/5 lg:w-4/5 h-auto px-6'>
            <span className='font-bold text-white text-xl lg:text-2xl flex items-center justify-start'><PiNotepadFill/> DDA</span>
        </div>
        <div className='w-3/5 md:w-2/5 lg:w-1/5 h-auto flex items-center justify-end px-4'>
          <button className='text-sm lg:text-lg font-bold text-white border p-2 rounded-lg hover:bg-white hover:text-black border-white active:bg-gray' onClick={() => setIdeaTabOpen(!ideaTabOpen)}>{ideaTabOpen ? "Cancel" : "New Idea"}</button>
        </div>
      </nav>
      {ideaTabOpen && (
        <div className='float-left overflow-auto relative w-full'>
          <AddIdea handleNewData={handleNewData} setIdeaTabOpen={setIdeaTabOpen}/>
        </div>
      )}
    </>
  )
}

export default Header

AddIdea.jsx

Le fichier AddIdea.jsx fournit le formulaire pour ajouter de nouveaux éléments, y compris les fonctionnalités de validation et de soumission.

Résumé :

  • Composant permettant d'ajouter de nouveaux éléments à la liste.

  • Utilise useState pour gérer les données de saisie du formulaire et le nombre de caractères.

  • Valide la longueur d'entrée et soumet de nouvelles données au composant parent.

Fonctions clés :

  • Gérer les modifications : Gère la saisie du formulaire et le nombre de caractères.

  • Gérer la soumission : Valide et soumet les données du formulaire au composant parent.

import React, { useState } from 'react';
import toast from "react-hot-toast";
import { Helmet } from 'react-helmet';

const AddIdea = ({ handleNewData, setIdeaTabOpen }) => {
    const maxLengths = 100;

    const [formData, setFormData] = useState();

    const [remainingChars, setRemainingChars] = useState(80)

    const handleChange = (e) => {
        if (e.target.value.length > maxLengths) {
            toast.error(`${`Input can't be more than ${maxLengths} characters`}`);
        } else {
            setFormData(e.target.value);
            setRemainingChars(maxLengths - e.target.value.length);
        }
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        if (!formData) {
            toast.error(`You don't have an idea.`);
            return
        }
        handleNewData(formData);
        setIdeaTabOpen(false)
    };

    return (
        <section className='h-screen'>
            <div className='flex items-center justify-center bg-blue rounded-b-xl border-b-[10px] py-8 lg:p-10'>
                <div className='m-1 w-full h-auto flex flex-wrap items-center justify-center'>
                    <div className='sm:w-[90%] w-3/5 h-auto'>
                        <textarea
                            className='w-full h-auto overflow-auto outline-none border p-2 sm:text-sm '
                            placeholder='Items...'
                            name="items"
                            value={formData}
                            maxLength={maxLengths}
                            onChange={handleChange}
                        ></textarea>
                        <p className={` text-xs ${remainingChars < 15 ? "text-red" : "text-white"}`}>{remainingChars} characters remaining</p>
                    </div>
                    <div className='sm:w-4/5 w-1/5 p-2 flex items-center justify-center'>
                        <button className='bg-primary_button text-white px-4 py-1 rounded-md font-bold' onClick={handleSubmit}>Submit</button>
                    </div>
                    <Helmet><title>Drag Drop & Arrange | New Idea</title></Helmet>
                </div>
            </div>
            <div className='w-full h-full fixed backdrop-blur-[1px]' onClick={()=>setIdeaTabOpen(false)}></div>
        </section>

    );
};

export default AddIdea;

DragDropArrange.jsx

Le fichier DragDropArrange.jsx est responsable de la gestion de la fonctionnalité glisser-déposer et de la mise à jour de l'ordre des éléments en fonction des interactions de l'utilisateur.

Summary:

  • Main component for handling drag-and-drop functionality.

  • Uses DndContext and SortableContext from @dnd-kit/core for drag-and-drop behavior.

  • Manages state for the data array and updates the order of items based on drag events.

  • Fetches initial data from projectData and sets it to the state.

Key Functions:

  • Handle Drag End: Manages the logic for rearranging items based on drag-and-drop actions.

  • Fetch Data: Fetches initial data and sets it to the component state.

import React, { useState, useEffect } from 'react';
import { DndContext, closestCenter } from '@dnd-kit/core';
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
import { Helmet } from 'react-helmet';
import Arrange from './Arrange';
import Loader from '../Navigation/Loader';

const DragDropArrange = ({projectData}) =>  {
  const [dataArray, setDataArray] = useState({
    newIdea: undefined,
    oldIdea: undefined,
    updateValue: []
  });

  const handleDragEnd = ({ active, over }) => {
    if (!over) return;

    const { fromList, targetId } = active.data.current;
    const { toList, index } = over.data.current;

    if (fromList === toList) {
      const sortedItems = arrayMove(dataArray[toList], dataArray[toList].findIndex((idea) => idea._id === targetId), index);
      setDataArray((prev) => ({ ...prev, [toList]: sortedItems }));
    } else {
      const draggedItem = dataArray[fromList].find((idea) => idea._id === targetId);
      const updatedFromList = dataArray[fromList].filter((idea) => idea._id !== targetId);
      const updatedToList = [...dataArray[toList].slice(0, index), draggedItem, ...dataArray[toList].slice(index)];

      setDataArray((prev) => ({ ...prev, [fromList]: updatedFromList, [toList]: updatedToList }));
    }
  };

  const fetchData = async () => {
    const { newIdea, oldIdea } = projectData;
    setTimeout(() => {
      setDataArray((prev) => ({ ...prev, newIdea, oldIdea }));
    }, 500);
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <section className='w-full h-auto md:w-11/12 lg:w-10/12 py-12 md:p-4 lg:p-12'>
      <div className='w-full h-auto my-2 overflow-auto'>
        <div className='w-full h-auto my-4 overflow-auto'>
          {dataArray.newIdea && dataArray.oldIdea && (
            <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
              <SortableContext items={dataArray?.newIdea}>
                <Arrange dataArray={dataArray} />
              </SortableContext>
            </DndContext>
          )}
          {!dataArray.newIdea && !dataArray.oldIdea && (
            <>
            <div className='w-full h-auto flex items-center justify-center'><Loader/></div>
            <div className='w-full text-center text-xl font-bold'>
              <span className='text-gradient'>Loading...</span>
            </div>
            </>
          )}
        </div>
      </div>
      <Helmet><title>Drag Drop & Arrange | Home</title></Helmet>
    </section>
  );
};

export default DragDropArrange

Arrange.jsx

The Arrange.jsx file handles the arrangement of new and old ideas, displaying them in sortable contexts.

Summary:

  • Manages the arrangement of new and old ideas.
  • Uses SortableContext for sortable behavior.

  • Displays items and manages their order within each category.

Key Functions:

  • Display Items: Renders items in their respective categories.

  • Handle Sorting: Manages the sortable behavior of items.

import React from 'react';
import { SortableContext } from '@dnd-kit/sortable';
import Drag from "./Drag";
import Drop from "./Drop";
import Lottie from 'react-lottie';
import NoData from '../../Lottie/NoData.json';

const Arrange = ({ dataArray }) => {
  const { newIdea, oldIdea } = dataArray;

  const defaultOptions = {
    loop: true,
    autoplay: true,
    animationData: NoData,
    rendererSettings: {
      preserveAspectRatio: "xMidYMid slice"
    }
  };

  return (
    <section className='w-full h-auto rounded-md overflow-auto'>
      <div className='h-auto overflow-auto flex flex-wrap items-start justify-around'>

        <div className='w-[48%] min-h-80 h-auto border border-blue shadow-md shadow-white-input-light rounded-md'>
          <h2 className='bg-blue text-white text-xl text-center font-extrabold py-2'>New Idea</h2>
          <SortableContext items={newIdea.map(item => item._id)}>
            {newIdea.length > 0 && (
              <>
                {newIdea?.map((data) => (
                  <React.Fragment key={data._id}>
                    <Drag data={data} listType="newIdea"/>
                  </React.Fragment>
                ))}
                <Drop index={newIdea.length} listType="newIdea" />
              </>
            )}
            {newIdea.length < 1 && (
              <>
                <div className='w-full h-52 flex items-center justify-center'>
                  <Lottie
                    options={defaultOptions}
                    height={150}
                    width={150}
                  />
                </div>
                <Drop index={newIdea.length} listType="newIdea" />
              </>
            )}
          </SortableContext>
        </div>

        <div className='w-[48%] min-h-80 h-auto border border-blue shadow-md shadow-white-input-light rounded-md'>
          <h2 className='bg-blue text-white text-xl text-center font-extrabold py-2'>Old Idea</h2>
          <SortableContext items={oldIdea.map(item => item._id)}>
            {oldIdea.length > 0 && (
              <>
                {oldIdea?.map((data) => (
                  <React.Fragment key={data._id}>
                    <Drag data={data} listType="oldIdea" />
                  </React.Fragment>
                ))}
                <Drop index={oldIdea.length} listType="oldIdea" />
              </>
            )}
            {oldIdea.length < 1 && (
              <>
                <div className='w-full h-52 flex items-center justify-center'>
                  <Lottie
                    options={defaultOptions}
                    height={150}
                    width={150}
                  />
                </div>
                <Drop index={oldIdea.length} listType="oldIdea" />
              </>
            )}
          </SortableContext>
        </div>

      </div>
    </section>
  );
}

export default Arrange

Drag.jsx

The Drag.jsx file manages the draggable items, defining their behavior and style during the drag operation.

Summary:

  • Manages the behavior and style of draggable items.

  • Uses useDraggable from @dnd-kit/core for draggable behavior.

  • Defines the drag and drop interaction for items.

Key Functions:

  • useDraggable: Provides drag-and-drop functionality.

  • Style Management: Updates the style based on drag state.

import React from 'react';
import { useDraggable } from '@dnd-kit/core';
import { IoMdMove } from "react-icons/io";

const Drag = ({ data, listType }) => {

    const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
        id: data._id,
        data: { fromList: listType, targetId: data._id },
    });

    const style = {
        transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined,
        opacity: isDragging ? 0.5 : 1,
        pointerEvents: isDragging ? 'none' : 'auto',
    };

    return (
        <>
            <section
                className="w-auto h-auto bg-yellow rounded-md border overflow-hidden m-2"
                ref={setNodeRef}
                style={style}
                {...listeners}
                {...attributes}
            >
                <div>
                    <div className="w-auto p-2 h-auto bg-yellow">
                        <p className="h-auto text-xs lg:text-sm text-black break-all">{data?.idea}</p>
                    </div>
                </div>
            </section>
        </>
    );
};

export default Drag;

Drop.jsx

The Drop.jsx file defines the droppable areas where items can be dropped, including visual feedback during the drag operation.

Summary:

  • Manages the behavior of droppable areas.
  • Uses useDroppable from @dnd-kit/core for droppable behavior.

  • Provides visual feedback during drag-and-drop interactions.

Key Functions:

  • useDroppable: Provides droppable functionality.

  • Handle Drop: Manages drop actions and updates the state accordingly.

import React from 'react';
import { useDroppable } from '@dnd-kit/core';

const Drop= ({ index, setDragged, listType }) => {
    const { isOver, setNodeRef } = useDroppable({
        id: `${listType}-${index}`,
        data: { toList: listType, index },
    });

    const handleDrop = (e) => {
        e.preventDefault();
        setDragged({ toList: listType, index });
    };

    return (
        <section
            ref={setNodeRef}
            onDrop={handleDrop}
            onDragOver={(e) => e.preventDefault()}
            className={`w-auto h-16 rounded-lg flex items-center justify-center text-xs text-secondary_shadow ${isOver ? ` opacity-100` : `opacity-0`}`}
            style={{ pointerEvents: 'none' }}
        >
        </section>
    );
};

export default Drop 

Conclusion

By following this comprehensive guide, you can create a dynamic and user-friendly drag-and-drop interface for your applications. This setup not only enhances user experience but also makes managing and organizing items intuitive and efficient. The combination of React, Tailwind CSS, and Dnd-kit provides a robust foundation for building such interactive features.

Feel free to customize and extend this implementation to suit your specific needs. Happy coding!

Source Code

You can find the complete source code for this project in my GitHub repository:
Github Link

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn