[codex] restore route priority drag behavior (#376)
* fix: restore route priority drag behavior * fix: use theme rail highlight surface
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { memo, useState, type ReactNode } from 'react';
|
import { memo, useState, type ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragOverlay,
|
|
||||||
KeyboardSensor,
|
KeyboardSensor,
|
||||||
PointerSensor,
|
PointerSensor,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
@@ -42,13 +41,12 @@ import {
|
|||||||
isExplicitGroupRoute,
|
isExplicitGroupRoute,
|
||||||
resolveRouteTitle,
|
resolveRouteTitle,
|
||||||
resolveRouteIcon,
|
resolveRouteIcon,
|
||||||
getPriorityTagStyle,
|
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import {
|
import {
|
||||||
buildPriorityBuckets,
|
buildPriorityBuckets,
|
||||||
} from './priorityBuckets.js';
|
} from './priorityBuckets.js';
|
||||||
import {
|
import {
|
||||||
buildPriorityDragPreviewStyle,
|
buildPriorityRailNodeStyle,
|
||||||
buildPriorityRailSections,
|
buildPriorityRailSections,
|
||||||
createPriorityRailNewLayerId,
|
createPriorityRailNewLayerId,
|
||||||
isPriorityRailNewLayerId,
|
isPriorityRailNewLayerId,
|
||||||
@@ -159,52 +157,6 @@ function PriorityRailNewLayerRow({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PriorityDragPreview({
|
|
||||||
channel,
|
|
||||||
displayPriority,
|
|
||||||
activeRowWidth,
|
|
||||||
}: {
|
|
||||||
channel: RouteChannel;
|
|
||||||
displayPriority: number;
|
|
||||||
activeRowWidth?: number | null;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
gap: 8,
|
|
||||||
padding: '8px 12px',
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
border: '1px solid var(--color-border)',
|
|
||||||
background: 'var(--color-bg-card)',
|
|
||||||
boxShadow: 'var(--shadow-sm)',
|
|
||||||
color: 'var(--color-text-primary)',
|
|
||||||
fontSize: 12,
|
|
||||||
...buildPriorityDragPreviewStyle(activeRowWidth),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="badge"
|
|
||||||
style={{
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: 0.1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`P${displayPriority}`}
|
|
||||||
</span>
|
|
||||||
<span style={{ fontWeight: 600 }}>
|
|
||||||
{channel.account?.username || `account-${channel.accountId}`}
|
|
||||||
</span>
|
|
||||||
<span className="badge badge-muted" style={{ fontSize: 10 }}>
|
|
||||||
{channel.site?.name || 'unknown'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RouteCardInner({
|
function RouteCardInner({
|
||||||
route,
|
route,
|
||||||
brand,
|
brand,
|
||||||
@@ -281,14 +233,10 @@ function RouteCardInner({
|
|||||||
const priorityBuckets = buildPriorityBuckets(channels || []);
|
const priorityBuckets = buildPriorityBuckets(channels || []);
|
||||||
const priorityRailSections = buildPriorityRailSections(channels || []);
|
const priorityRailSections = buildPriorityRailSections(channels || []);
|
||||||
const [activeDragChannelId, setActiveDragChannelId] = useState<number | null>(null);
|
const [activeDragChannelId, setActiveDragChannelId] = useState<number | null>(null);
|
||||||
const [activeDragRowWidth, setActiveDragRowWidth] = useState<number | null>(null);
|
|
||||||
const [dragOverId, setDragOverId] = useState<string | number | null>(null);
|
const [dragOverId, setDragOverId] = useState<string | number | null>(null);
|
||||||
const activeDragChannel = activeDragChannelId == null
|
const activeDragBucketIndex = activeDragChannelId == null
|
||||||
? null
|
|
||||||
: (channels || []).find((channel) => channel.id === activeDragChannelId) || null;
|
|
||||||
const activeDragBucketIndex = activeDragChannel == null
|
|
||||||
? -1
|
? -1
|
||||||
: priorityBuckets.findIndex((bucket) => bucket.channels.some((channel) => channel.id === activeDragChannel.id));
|
: priorityBuckets.findIndex((bucket) => bucket.channels.some((channel) => channel.id === activeDragChannelId));
|
||||||
const hoveredBucketIndex = typeof dragOverId === 'number'
|
const hoveredBucketIndex = typeof dragOverId === 'number'
|
||||||
? priorityBuckets.findIndex((bucket) => bucket.channels.some((channel) => channel.id === dragOverId))
|
? priorityBuckets.findIndex((bucket) => bucket.channels.some((channel) => channel.id === dragOverId))
|
||||||
: -1;
|
: -1;
|
||||||
@@ -298,14 +246,12 @@ function RouteCardInner({
|
|||||||
|
|
||||||
const clearDragState = () => {
|
const clearDragState = () => {
|
||||||
setActiveDragChannelId(null);
|
setActiveDragChannelId(null);
|
||||||
setActiveDragRowWidth(null);
|
|
||||||
setDragOverId(null);
|
setDragOverId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragStart = (event: DragStartEvent) => {
|
const handleDragStart = (event: DragStartEvent) => {
|
||||||
const nextId = Number(event.active.id);
|
const nextId = Number(event.active.id);
|
||||||
setActiveDragChannelId(Number.isFinite(nextId) ? nextId : null);
|
setActiveDragChannelId(Number.isFinite(nextId) ? nextId : null);
|
||||||
setActiveDragRowWidth(event.active.rect.current.initial?.width ?? null);
|
|
||||||
setDragOverId(event.active.id);
|
setDragOverId(event.active.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -679,7 +625,7 @@ function RouteCardInner({
|
|||||||
const hoveredExistingLayer = activeDragChannelId != null && hoveredBucketIndex === bucketIndex;
|
const hoveredExistingLayer = activeDragChannelId != null && hoveredBucketIndex === bucketIndex;
|
||||||
const hoveredCrossLayer = hoveredExistingLayer && activeDragBucketIndex !== bucketIndex;
|
const hoveredCrossLayer = hoveredExistingLayer && activeDragBucketIndex !== bucketIndex;
|
||||||
const hoveredNewLayer = activeDragChannelId != null && hoveredNewLayerBucketIndex === bucketIndex;
|
const hoveredNewLayer = activeDragChannelId != null && hoveredNewLayerBucketIndex === bucketIndex;
|
||||||
const railTone = getPriorityTagStyle(bucketIndex);
|
const railNodeStyle = buildPriorityRailNodeStyle(bucketIndex, hoveredExistingLayer);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -739,16 +685,12 @@ function RouteCardInner({
|
|||||||
minWidth: 72,
|
minWidth: 72,
|
||||||
padding: '6px 10px',
|
padding: '6px 10px',
|
||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
border: `1px solid ${hoveredExistingLayer ? 'var(--color-primary)' : 'color-mix(in srgb, currentColor 24%, transparent)'}`,
|
|
||||||
background: hoveredExistingLayer
|
|
||||||
? 'color-mix(in srgb, var(--color-primary) 10%, var(--color-bg))'
|
|
||||||
: railTone.background,
|
|
||||||
color: hoveredExistingLayer ? 'var(--color-primary)' : railTone.color,
|
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
transition: 'all 0.16s ease',
|
transition: 'all 0.16s ease',
|
||||||
|
...railNodeStyle,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{railSection ? `P${bucketIndex} · ${railSection.channelCount}` : railLabel}
|
{railSection ? `P${bucketIndex} · ${railSection.channelCount}` : railLabel}
|
||||||
@@ -806,15 +748,6 @@ function RouteCardInner({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
<DragOverlay>
|
|
||||||
{activeDragChannel && !compact ? (
|
|
||||||
<PriorityDragPreview
|
|
||||||
channel={activeDragChannel}
|
|
||||||
displayPriority={Math.max(0, activeDragBucketIndex)}
|
|
||||||
activeRowWidth={activeDragRowWidth}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</DragOverlay>
|
|
||||||
</DndContext>
|
</DndContext>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function SortableChannelRow({
|
|||||||
const rowStyle: CSSProperties = {
|
const rowStyle: CSSProperties = {
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
transition,
|
transition,
|
||||||
opacity: isDragging ? (mobile ? 0.72 : 0) : channel.enabled === false ? 0.5 : 1,
|
opacity: isDragging ? 0.72 : channel.enabled === false ? 0.5 : 1,
|
||||||
zIndex: isDragging ? 10 : 1,
|
zIndex: isDragging ? 10 : 1,
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: managementLocked || mobile ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto auto auto',
|
gridTemplateColumns: managementLocked || mobile ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto auto auto',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
applyPriorityRailDrop,
|
applyPriorityRailDrop,
|
||||||
buildPriorityDragPreviewStyle,
|
buildPriorityRailNodeStyle,
|
||||||
buildPriorityRailDragTargets,
|
buildPriorityRailDragTargets,
|
||||||
buildPriorityRailSections,
|
buildPriorityRailSections,
|
||||||
createPriorityRailNewLayerId,
|
createPriorityRailNewLayerId,
|
||||||
@@ -76,10 +76,15 @@ describe('priorityRail helpers', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the active row width for drag preview alignment when available', () => {
|
it('keeps priority color hierarchy even when a rail node is highlighted', () => {
|
||||||
expect(buildPriorityDragPreviewStyle(640)).toMatchObject({
|
const p0 = buildPriorityRailNodeStyle(0, false);
|
||||||
width: 640,
|
const p0Highlighted = buildPriorityRailNodeStyle(0, true);
|
||||||
maxWidth: 'calc(100vw - 32px)',
|
const p1Highlighted = buildPriorityRailNodeStyle(1, true);
|
||||||
});
|
|
||||||
|
expect(p0.background).not.toBe('var(--color-bg)');
|
||||||
|
expect(String(p0Highlighted.background)).toContain('var(--color-bg)');
|
||||||
|
expect(String(p0Highlighted.background)).not.toContain('white');
|
||||||
|
expect(p0Highlighted.color).toBe('var(--color-primary)');
|
||||||
|
expect(p0Highlighted.background).not.toBe(p1Highlighted.background);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import type { PriorityRailDragTarget, PriorityRailSection } from './types.js';
|
import type { PriorityRailDragTarget, PriorityRailSection } from './types.js';
|
||||||
|
import { getPriorityTagStyle } from './utils.js';
|
||||||
|
|
||||||
type PriorityRailChannelLike = {
|
type PriorityRailChannelLike = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -119,10 +120,14 @@ export function applyPriorityRailDrop<T extends PriorityRailChannelLike>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildPriorityDragPreviewStyle(activeRowWidth?: number | null): CSSProperties {
|
export function buildPriorityRailNodeStyle(priority: number, highlighted: boolean): CSSProperties {
|
||||||
const resolvedWidth = Number.isFinite(activeRowWidth ?? Number.NaN) ? activeRowWidth ?? undefined : undefined;
|
const tone = getPriorityTagStyle(priority);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: resolvedWidth,
|
border: `1px solid ${highlighted ? 'var(--color-primary)' : 'color-mix(in srgb, currentColor 24%, transparent)'}`,
|
||||||
maxWidth: 'calc(100vw - 32px)',
|
background: highlighted
|
||||||
|
? `color-mix(in srgb, ${tone.background} 78%, var(--color-bg))`
|
||||||
|
: tone.background,
|
||||||
|
color: highlighted ? 'var(--color-primary)' : tone.color,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user