feat: emoji picker in create dialogs; due date and priority right under a deepened task; no flicker on kanban drop
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m29s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 14:20:34 +01:00
1 parent fa02e8dee6
commit 7860609201
13 files changed
+143 -74

No files matched your search

+18 -16
View File
@@ -41,6 +41,7 @@ import { ManageColumnsModal, DEFAULT_KANBAN_COLUMNS, columnDotClass } from './Ma
import { AvatarStack, AvatarPerson } from '../../components/AvatarStack';
import { CountBadge } from '../../components/CountBadge';
import { FormSheet } from '../../components/FormSheet';
import { EmojiIconButton } from '../../components/EmojiIconButton';
import { useMobileActions } from '../../context/MobileActionContext';
// Drag & drop needs a mouse or pen; on touch-only devices a draggable row can swallow a slightly
@@ -924,9 +925,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
const renderDropSlot = (colId: string, index: number) => (
<motion.div
key={`slot-${colId}-${index}`}
initial={{ height: 0, opacity: 0 }}
animate={{ height: dragHeightRef.current, opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
variants={{
closed: { height: 0, opacity: 0 },
open: { height: dragHeightRef.current, opacity: 1 },
// On a drop the card takes the gap's place at once, so the gap goes without closing first
gone: (dropped: boolean) => ({ height: 0, opacity: 0, transition: { duration: dropped ? 0 : 0.2 } }),
}}
initial="closed"
animate="open"
exit="gone"
transition={{ duration: 0.2, ease: 'easeOut' }}
className="rounded-card border-2 border-dashed border-accent/50 bg-accent/5 pointer-events-none"
aria-hidden="true"
@@ -967,6 +974,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
const slot = kanbanSlot;
resetKanbanDrag();
if (!sourceId) return;
// Set with the move in one render: the card shows up marked in its new place, not a moment later
setJustDroppedId(sourceId);
window.setTimeout(() => setJustDroppedId((cur) => (cur === sourceId ? null : cur)), 900);
const others = colTasks.filter((t) => t.id !== sourceId);
if (others.length === 0 || !slot || slot.col !== colId) {
await moveTaskToColumn(sourceId, colId);
@@ -975,8 +985,6 @@ export const TasksView: React.FC<TasksViewProps> = ({
} else {
await moveTaskNextTo(sourceId, others[others.length - 1], 'after', colId);
}
setJustDroppedId(sourceId);
window.setTimeout(() => setJustDroppedId((cur) => (cur === sourceId ? null : cur)), 900);
};
// A drag that ends outside any column (Escape, dropped elsewhere) leaves nothing behind
@@ -2285,14 +2293,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
<div>
<label className="block text-xs text-muted mb-1">Titel der Liste</label>
<div className="flex items-center gap-1.5">
<input
type="text"
value={newListIcon}
onChange={(e) => setNewListIcon(e.target.value)}
maxLength={2}
className="w-12 sm:w-10 h-11 sm:h-8 text-center input-field text-base sm:text-sm"
title="Icon (Emoji)"
/>
<EmojiIconButton value={newListIcon} onChange={setNewListIcon} fallback="📋" />
<input
type="text"
value={newListTitle}
@@ -2458,13 +2459,14 @@ export const TasksView: React.FC<TasksViewProps> = ({
return (
<React.Fragment key={task.id}>
<AnimatePresence initial={false}>
<AnimatePresence initial={false} custom={justDroppedId !== null}>
{slotHere >= 0 && slotHere === visibleIdx && renderDropSlot(col.id, visibleIdx)}
</AnimatePresence>
<div
draggable={canDragTasks && !readOnly}
onDragStart={(e) => {
handleTaskDragStart(e, task);
setJustDroppedId(null);
dragHeightRef.current = e.currentTarget.getBoundingClientRect().height;
// Hidden a tick later: hiding the source right away would cancel the drag
window.setTimeout(() => setKanbanHiddenId(task.id), 0);
@@ -2481,7 +2483,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
readOnly ? 'cursor-pointer' : 'cursor-grab active:cursor-grabbing'
} ${
kanbanHiddenId === task.id ? 'hidden' : isDragging ? 'opacity-30 border border-dashed border-accent/40' : ''
} ${justDroppedId === task.id ? 'ring-2 ring-accent/60 animate-in fade-in zoom-in-95 duration-300' : ''}`}
} ${justDroppedId === task.id ? 'ring-2 ring-accent/60' : ''}`}
>
<div className="flex items-start justify-between gap-2">
<p
@@ -2629,7 +2631,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
</React.Fragment>
);
})}
<AnimatePresence initial={false}>
<AnimatePresence initial={false} custom={justDroppedId !== null}>
{slotHere >= 0 && slotHere === visibleIds.length && renderDropSlot(col.id, visibleIds.length)}
</AnimatePresence>