Quellcode des ICON CRM (6)
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { useCallback, useState, type DragEvent } from 'react';
|
||||
|
||||
/*
|
||||
* Ziehen und Fallenlassen auf den Kanban-Brettern (Leads, Ausschreibungen).
|
||||
*
|
||||
* Die Gesten stecken hier und nicht in den Ansichten, weil sie dreimal an
|
||||
* denselben Stellen schiefgehen:
|
||||
*
|
||||
* 1. **Ohne Nutzlast keine Geste.** Firefox startet eine Ziehgeste nur, wenn
|
||||
* `dragstart` etwas in `dataTransfer` legt. Chrome ist da nachsichtiger -
|
||||
* deshalb faellt es beim Entwickeln nicht auf und beim Benutzen sofort.
|
||||
* 2. **Der Zustand allein reicht nicht.** Die Kennung wird zusaetzlich in
|
||||
* `dataTransfer` mitgegeben. Sie ueberlebt damit auch einen Neuaufbau der
|
||||
* Liste waehrend der Geste.
|
||||
* 3. **Ohne `dropEffect` zeigt der Zeiger ein Verbotsschild**, obwohl das
|
||||
* Fallenlassen erlaubt ist.
|
||||
*
|
||||
* Bilder in den Kacheln (Logo, Foto) muessen zusaetzlich `draggable={false}`
|
||||
* tragen - sonst zieht der Browser das Bild statt der Kachel. Das erledigt die
|
||||
* Avatar-Komponente.
|
||||
*/
|
||||
|
||||
/** Eigenes Format neben `text/plain`: erkennt eigene Kacheln von fremdem Text. */
|
||||
const MIME = 'application/x-icon-card';
|
||||
|
||||
interface KanbanDrag<K extends string> {
|
||||
/** Kennung der Kachel am Zeiger - fuer die gedaempfte Darstellung. */
|
||||
draggedId: number | null;
|
||||
/** Spalte unter dem Zeiger - fuer die Hervorhebung des Ziels. */
|
||||
hoverColumn: K | null;
|
||||
/** Auf die Kachel legen. */
|
||||
cardProps: (id: number) => {
|
||||
draggable: true;
|
||||
onDragStart: (event: DragEvent<HTMLElement>) => void;
|
||||
onDragEnd: () => void;
|
||||
};
|
||||
/** Auf die Spalte legen. */
|
||||
columnProps: (column: K) => {
|
||||
onDragOver: (event: DragEvent<HTMLElement>) => void;
|
||||
onDragLeave: (event: DragEvent<HTMLElement>) => void;
|
||||
onDrop: (event: DragEvent<HTMLElement>) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function useKanbanDrag<K extends string>(
|
||||
onMove: (id: number, column: K) => void
|
||||
): KanbanDrag<K> {
|
||||
const [draggedId, setDraggedId] = useState<number | null>(null);
|
||||
const [hoverColumn, setHoverColumn] = useState<K | null>(null);
|
||||
|
||||
const cardProps = useCallback(
|
||||
(id: number) =>
|
||||
({
|
||||
draggable: true,
|
||||
onDragStart: (event: DragEvent<HTMLElement>) => {
|
||||
event.dataTransfer.setData(MIME, String(id));
|
||||
event.dataTransfer.setData('text/plain', String(id));
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
setDraggedId(id);
|
||||
},
|
||||
onDragEnd: () => {
|
||||
setDraggedId(null);
|
||||
setHoverColumn(null);
|
||||
},
|
||||
}) as const,
|
||||
[]
|
||||
);
|
||||
|
||||
const columnProps = useCallback(
|
||||
(column: K) => ({
|
||||
onDragOver: (event: DragEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
setHoverColumn((current) => (current === column ? current : column));
|
||||
},
|
||||
onDragLeave: (event: DragEvent<HTMLElement>) => {
|
||||
// Der Zeiger wandert staendig ueber Kindelemente - nur das Verlassen
|
||||
// der Spalte selbst zaehlt.
|
||||
if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;
|
||||
setHoverColumn((current) => (current === column ? null : current));
|
||||
},
|
||||
onDrop: (event: DragEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
const carried = event.dataTransfer.getData(MIME) || event.dataTransfer.getData('text/plain');
|
||||
const id = draggedId ?? Number(carried);
|
||||
setDraggedId(null);
|
||||
setHoverColumn(null);
|
||||
if (Number.isInteger(id) && id > 0) onMove(id, column);
|
||||
},
|
||||
}),
|
||||
[draggedId, onMove]
|
||||
);
|
||||
|
||||
return { draggedId, hoverColumn, cardProps, columnProps };
|
||||
}
|
||||
|
||||
/** Rahmen der Spalte, solange eine Kachel darueber schwebt. */
|
||||
export function columnClass(isHover: boolean): string {
|
||||
return isHover ? 'border-brand-400 bg-brand-50/40' : 'border-ink-200 bg-canvas';
|
||||
}
|
||||
|
||||
/** Die Kachel am Zeiger wird gedaempft - sonst sieht man nicht, was man zieht. */
|
||||
export function cardClass(isDragged: boolean): string {
|
||||
return isDragged ? 'opacity-40' : '';
|
||||
}
|
||||
Reference in New Issue
Block a user