1
0
forked from boos/BoosAPI

[codex] restore route priority drag behavior (#376)

* fix: restore route priority drag behavior

* fix: use theme rail highlight surface
This commit is contained in:
Cita
2026-04-01 21:24:25 +08:00
committed by GitHub
parent 5fffcb6d66
commit 1133846998
4 changed files with 26 additions and 83 deletions
+5 -72
View File
@@ -1,7 +1,6 @@
import { memo, useState, type ReactNode } from 'react';
import {
DndContext,
DragOverlay,
KeyboardSensor,
PointerSensor,
closestCenter,
@@ -42,13 +41,12 @@ import {
isExplicitGroupRoute,
resolveRouteTitle,
resolveRouteIcon,
getPriorityTagStyle,
} from './utils.js';
import {
buildPriorityBuckets,
} from './priorityBuckets.js';
import {
buildPriorityDragPreviewStyle,
buildPriorityRailNodeStyle,
buildPriorityRailSections,
createPriorityRailNewLayerId,
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({
route,
brand,
@@ -281,14 +233,10 @@ function RouteCardInner({
const priorityBuckets = buildPriorityBuckets(channels || []);
const priorityRailSections = buildPriorityRailSections(channels || []);
const [activeDragChannelId, setActiveDragChannelId] = useState<number | null>(null);
const [activeDragRowWidth, setActiveDragRowWidth] = useState<number | null>(null);
const [dragOverId, setDragOverId] = useState<string | number | null>(null);
const activeDragChannel = activeDragChannelId == null
? null
: (channels || []).find((channel) => channel.id === activeDragChannelId) || null;
const activeDragBucketIndex = activeDragChannel == null
const activeDragBucketIndex = activeDragChannelId == null
? -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'
? priorityBuckets.findIndex((bucket) => bucket.channels.some((channel) => channel.id === dragOverId))
: -1;
@@ -298,14 +246,12 @@ function RouteCardInner({
const clearDragState = () => {
setActiveDragChannelId(null);
setActiveDragRowWidth(null);
setDragOverId(null);
};
const handleDragStart = (event: DragStartEvent) => {
const nextId = Number(event.active.id);
setActiveDragChannelId(Number.isFinite(nextId) ? nextId : null);
setActiveDragRowWidth(event.active.rect.current.initial?.width ?? null);
setDragOverId(event.active.id);
};
@@ -679,7 +625,7 @@ function RouteCardInner({
const hoveredExistingLayer = activeDragChannelId != null && hoveredBucketIndex === bucketIndex;
const hoveredCrossLayer = hoveredExistingLayer && activeDragBucketIndex !== bucketIndex;
const hoveredNewLayer = activeDragChannelId != null && hoveredNewLayerBucketIndex === bucketIndex;
const railTone = getPriorityTagStyle(bucketIndex);
const railNodeStyle = buildPriorityRailNodeStyle(bucketIndex, hoveredExistingLayer);
return (
<div
@@ -739,16 +685,12 @@ function RouteCardInner({
minWidth: 72,
padding: '6px 10px',
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,
fontWeight: 600,
textAlign: 'center',
lineHeight: 1.2,
transition: 'all 0.16s ease',
...railNodeStyle,
}}
>
{railSection ? `P${bucketIndex} · ${railSection.channelCount}` : railLabel}
@@ -806,15 +748,6 @@ function RouteCardInner({
})}
</div>
</SortableContext>
<DragOverlay>
{activeDragChannel && !compact ? (
<PriorityDragPreview
channel={activeDragChannel}
displayPriority={Math.max(0, activeDragBucketIndex)}
activeRowWidth={activeDragRowWidth}
/>
) : null}
</DragOverlay>
</DndContext>
</div>
) : (
@@ -50,7 +50,7 @@ export function SortableChannelRow({
const rowStyle: CSSProperties = {
transform: CSS.Transform.toString(transform),
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,
display: 'grid',
gridTemplateColumns: managementLocked || mobile ? 'minmax(0, 1fr)' : 'minmax(0, 1fr) auto auto auto',
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import {
applyPriorityRailDrop,
buildPriorityDragPreviewStyle,
buildPriorityRailNodeStyle,
buildPriorityRailDragTargets,
buildPriorityRailSections,
createPriorityRailNewLayerId,
@@ -76,10 +76,15 @@ describe('priorityRail helpers', () => {
]);
});
it('uses the active row width for drag preview alignment when available', () => {
expect(buildPriorityDragPreviewStyle(640)).toMatchObject({
width: 640,
maxWidth: 'calc(100vw - 32px)',
});
it('keeps priority color hierarchy even when a rail node is highlighted', () => {
const p0 = buildPriorityRailNodeStyle(0, false);
const p0Highlighted = buildPriorityRailNodeStyle(0, true);
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);
});
});
+9 -4
View File
@@ -1,5 +1,6 @@
import type { CSSProperties } from 'react';
import type { PriorityRailDragTarget, PriorityRailSection } from './types.js';
import { getPriorityTagStyle } from './utils.js';
type PriorityRailChannelLike = {
id: number;
@@ -119,10 +120,14 @@ export function applyPriorityRailDrop<T extends PriorityRailChannelLike>(
);
}
export function buildPriorityDragPreviewStyle(activeRowWidth?: number | null): CSSProperties {
const resolvedWidth = Number.isFinite(activeRowWidth ?? Number.NaN) ? activeRowWidth ?? undefined : undefined;
export function buildPriorityRailNodeStyle(priority: number, highlighted: boolean): CSSProperties {
const tone = getPriorityTagStyle(priority);
return {
width: resolvedWidth,
maxWidth: 'calc(100vw - 32px)',
border: `1px solid ${highlighted ? 'var(--color-primary)' : 'color-mix(in srgb, currentColor 24%, transparent)'}`,
background: highlighted
? `color-mix(in srgb, ${tone.background} 78%, var(--color-bg))`
: tone.background,
color: highlighted ? 'var(--color-primary)' : tone.color,
};
}