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:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user