style(layout): the whole row is the drag handle
A list whose rows can only be moved by a sixteen-pixel grip is a list most people conclude cannot be moved at all. The grip stays as the sign that it can, and the row itself now carries the gesture — plus a line on the edge the row would take, because the question a dragging hand asks is 'between which two', which a highlighted target does not answer.
This commit is contained in:
@@ -333,6 +333,10 @@ function WidgetOrderSection() {
|
||||
const [order, setOrder] = useState<string[]>(readWidgetOrder);
|
||||
const dragKey = useRef<string | null>(null);
|
||||
const [dragging, setDragging] = useState<string | null>(null);
|
||||
// Where the row would land. Drawn as a line above the target rather than by
|
||||
// colouring it: the question a dragging hand asks is "between which two", and
|
||||
// a highlighted row answers a different one.
|
||||
const [over, setOver] = useState<string | null>(null);
|
||||
|
||||
const commit = (keys: string[]) => {
|
||||
setOrder(keys);
|
||||
@@ -364,18 +368,32 @@ function WidgetOrderSection() {
|
||||
</div>
|
||||
))}
|
||||
{order.map((k) => (
|
||||
<div key={k}
|
||||
onDragOver={(e) => { if (dragKey.current) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } }}
|
||||
onDrop={(e) => { if (dragKey.current) { e.preventDefault(); moveTo(dragKey.current, k); } }}
|
||||
className={cn('flex items-center gap-2 rounded-md border border-border bg-card px-2 py-1.5 text-sm',
|
||||
dragging === k && 'opacity-50')}>
|
||||
<span draggable
|
||||
onDragStart={(e) => { dragKey.current = k; setDragging(k); e.dataTransfer.effectAllowed = 'move'; }}
|
||||
onDragEnd={() => { dragKey.current = null; setDragging(null); }}
|
||||
title={t('wo.drag')}
|
||||
className="cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-foreground">
|
||||
<GripVertical className="size-4" />
|
||||
</span>
|
||||
// The WHOLE row is the handle, not the grip alone: a list whose rows
|
||||
// can only be moved by a 16-pixel icon is a list most people conclude
|
||||
// cannot be moved. The grip stays as the sign that it can.
|
||||
<div key={k} draggable
|
||||
onDragStart={(e) => { dragKey.current = k; setDragging(k); e.dataTransfer.effectAllowed = 'move'; }}
|
||||
onDragEnd={() => { dragKey.current = null; setDragging(null); setOver(null); }}
|
||||
onDragOver={(e) => {
|
||||
if (!dragKey.current) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
if (over !== k) setOver(k);
|
||||
}}
|
||||
onDragLeave={() => { if (over === k) setOver(null); }}
|
||||
onDrop={(e) => {
|
||||
if (!dragKey.current) return;
|
||||
e.preventDefault();
|
||||
moveTo(dragKey.current, k);
|
||||
setOver(null);
|
||||
}}
|
||||
title={t('wo.drag')}
|
||||
className={cn('flex items-center gap-2 rounded-md border bg-card px-2 py-1.5 text-sm select-none',
|
||||
'cursor-grab active:cursor-grabbing transition-shadow',
|
||||
dragging === k ? 'opacity-50 border-primary shadow-lg' : 'border-border hover:border-foreground/30',
|
||||
// The landing line, on the edge the row would take.
|
||||
over === k && dragging !== k && 'shadow-[inset_0_3px_0_0_var(--primary)]')}>
|
||||
<GripVertical className="size-4 shrink-0 text-muted-foreground/50" />
|
||||
<span className="flex-1 min-w-0 truncate">{t(WIDGET_LABELS[k] ?? k)}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user