mirror of
https://github.com/documenso/documenso.git
synced 2026-08-25 07:42:28 +10:00
fix: wip
This commit is contained in:
@@ -14,6 +14,23 @@ type TEditorSigner = TEditorRecipientsFormSchema['signers'][number];
|
||||
|
||||
export type DraggingType = 'STEP' | 'RECIPIENT' | null;
|
||||
|
||||
/**
|
||||
* Skips the drop animation. The post-drop state update re-sorts and renumbers
|
||||
* the groups anyway, so gliding to the predicted slot first makes every drop
|
||||
* feel like it settles twice — snapping hands control to the real re-render
|
||||
* immediately instead.
|
||||
*/
|
||||
const getDraggableStyle = (provided: DraggableProvided, snapshot: DraggableStateSnapshot) => {
|
||||
if (!snapshot.isDropAnimating) {
|
||||
return provided.draggableProps.style;
|
||||
}
|
||||
|
||||
return {
|
||||
...provided.draggableProps.style,
|
||||
transitionDuration: '0.001s',
|
||||
};
|
||||
};
|
||||
|
||||
export type RecipientStepCardSharedRowProps = Pick<
|
||||
RecipientRowProps,
|
||||
| 'showAdvancedSettings'
|
||||
@@ -28,6 +45,7 @@ export type RecipientStepCardSharedRowProps = Pick<
|
||||
export type RecipientStepCardProps = {
|
||||
stepIndex: number;
|
||||
step: RecipientStep<TEditorSigner>;
|
||||
isLastStep: boolean;
|
||||
draggableProvided: DraggableProvided;
|
||||
draggableSnapshot: DraggableStateSnapshot;
|
||||
draggingType: DraggingType;
|
||||
@@ -40,9 +58,52 @@ export type RecipientStepCardProps = {
|
||||
rowProps: RecipientStepCardSharedRowProps;
|
||||
};
|
||||
|
||||
/**
|
||||
* The drop-zone strip rendered above each group card (and below the last one)
|
||||
* that receives recipient-row drops. Invisible until a dragged row hovers it,
|
||||
* then it shows a full-width green line marking the insertion point.
|
||||
*
|
||||
* Notes:
|
||||
* - It lives INSIDE the step's Draggable so it shifts together with the card
|
||||
* while groups are being reordered — a static strip between draggables
|
||||
* would stay behind while the cards around it are displaced, making group
|
||||
* drags look broken.
|
||||
* - Its `droppableId` must stay STABLE while mounted (anchored to a formId,
|
||||
* never a positional index): @hello-pangea/dnd does not support changing
|
||||
* ids on mounted droppables/draggables, which silently breaks them.
|
||||
* - `type="RECIPIENT"` already scopes it to recipient-row drags, and
|
||||
* `isDropDisabled` must not be toggled based on the active drag, as
|
||||
* @hello-pangea/dnd snapshots it at drag start (before state updates land).
|
||||
* - It must keep a CONSTANT size: droppable geometry is captured when a drag
|
||||
* starts, so resizing during the drag would leave the visible strip and the
|
||||
* actual hit area in different places. Only colors may change mid-drag.
|
||||
*/
|
||||
const RecipientStepGap = ({ droppableId }: { droppableId: string }) => (
|
||||
<Droppable droppableId={droppableId} type="RECIPIENT">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
data-testid="recipient-step-gap"
|
||||
className={cn('flex h-6 items-center', {
|
||||
'gap-active': snapshot.isDraggingOver,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn('h-[3px] w-full rounded-full bg-primary opacity-0 transition-opacity duration-100', {
|
||||
'opacity-100': snapshot.isDraggingOver,
|
||||
})}
|
||||
/>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
|
||||
export const RecipientStepCard = ({
|
||||
stepIndex,
|
||||
step,
|
||||
isLastStep,
|
||||
draggableProvided,
|
||||
draggableSnapshot,
|
||||
draggingType,
|
||||
@@ -57,20 +118,23 @@ export const RecipientStepCard = ({
|
||||
const isGroup = step.members.length > 1;
|
||||
const isCombineTarget = draggingType === 'STEP' && Boolean(draggableSnapshot.combineTargetFor);
|
||||
|
||||
// All droppable ids are anchored to the first member's formId (never a
|
||||
// positional index) so they stay stable while cards are reordered —
|
||||
// @hello-pangea/dnd does not support changing ids on mounted elements.
|
||||
const stepAnchor = step.members[0].formId;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
{...draggableProvided.draggableProps}
|
||||
className={cn('py-1', {
|
||||
style={getDraggableStyle(draggableProvided, draggableSnapshot)}
|
||||
className={cn({
|
||||
'pointer-events-none': draggableSnapshot.isDragging,
|
||||
})}
|
||||
>
|
||||
{/*
|
||||
Note: `type="RECIPIENT"` already scopes this droppable to recipient-row
|
||||
drags — `isDropDisabled` must not be toggled based on the active drag,
|
||||
as @hello-pangea/dnd snapshots it at drag start.
|
||||
*/}
|
||||
<Droppable droppableId={`step-members-${stepIndex}`} type="RECIPIENT">
|
||||
<RecipientStepGap droppableId={`gap-${stepAnchor}`} />
|
||||
|
||||
<Droppable droppableId={`step-members-${stepAnchor}`} type="RECIPIENT">
|
||||
{(droppableProvided, droppableSnapshot) => {
|
||||
const isJoinTarget = draggingType === 'RECIPIENT' && droppableSnapshot.isDraggingOver;
|
||||
const isHighlighted = isCombineTarget || isJoinTarget;
|
||||
@@ -80,9 +144,9 @@ export const RecipientStepCard = ({
|
||||
ref={droppableProvided.innerRef}
|
||||
{...droppableProvided.droppableProps}
|
||||
data-testid="recipient-step-card"
|
||||
className={cn('relative rounded-lg border px-3 pt-2 pb-1', {
|
||||
className={cn('relative rounded-lg border bg-background px-3 pt-2 pb-1 transition-shadow', {
|
||||
'border-primary/60 bg-primary/5': isGroup,
|
||||
'bg-widget-foreground': draggableSnapshot.isDragging,
|
||||
'bg-widget-foreground shadow-lg': draggableSnapshot.isDragging,
|
||||
'border-primary ring-1 ring-primary': isHighlighted,
|
||||
})}
|
||||
>
|
||||
@@ -93,28 +157,31 @@ export const RecipientStepCard = ({
|
||||
className="absolute -top-3 right-4 z-10 flex items-center gap-x-1 shadow-sm"
|
||||
>
|
||||
<Users2Icon className="h-3 w-3" />
|
||||
<Trans>Release to sign together</Trans>
|
||||
<Trans>Release to group</Trans>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row items-center gap-x-2">
|
||||
<div className="flex flex-row items-center gap-x-1">
|
||||
<span
|
||||
{...(draggableProvided.dragHandleProps ?? {})}
|
||||
data-testid="step-drag-handle"
|
||||
className={cn({ 'pointer-events-none opacity-30': isStepLocked })}
|
||||
className={cn(
|
||||
'-my-1 -ml-1.5 flex h-8 w-8 flex-shrink-0 cursor-grab items-center justify-center rounded-md hover:bg-foreground/5 active:cursor-grabbing',
|
||||
{ 'pointer-events-none opacity-30': isStepLocked },
|
||||
)}
|
||||
>
|
||||
<GripVerticalIcon className="h-4 w-4 opacity-40" />
|
||||
<GripVerticalIcon className="h-4 w-4 opacity-60" />
|
||||
</span>
|
||||
|
||||
<Badge variant="neutral" size="small">
|
||||
<Badge variant={isGroup ? 'default' : 'neutral'} size="small">
|
||||
<Trans>Group {step.order}</Trans>
|
||||
</Badge>
|
||||
|
||||
{isGroup && (
|
||||
<>
|
||||
<span className="flex items-center gap-x-1.5 text-muted-foreground text-xs">
|
||||
<span className="ml-1 flex items-center gap-x-1.5 text-green-700 text-xs dark:text-green-400">
|
||||
<Users2Icon className="h-3.5 w-3.5" />
|
||||
<Trans>{step.members.length} signers · any order</Trans>
|
||||
<Trans>{step.members.length} recipients · any order</Trans>
|
||||
</span>
|
||||
|
||||
<Button
|
||||
@@ -147,8 +214,9 @@ export const RecipientStepCard = ({
|
||||
<div
|
||||
ref={memberProvided.innerRef}
|
||||
{...memberProvided.draggableProps}
|
||||
style={getDraggableStyle(memberProvided, memberSnapshot)}
|
||||
className={cn({
|
||||
'rounded-md bg-widget-foreground': memberSnapshot.isDragging,
|
||||
'rounded-md bg-widget-foreground shadow-lg': memberSnapshot.isDragging,
|
||||
})}
|
||||
>
|
||||
<RecipientRow
|
||||
@@ -172,6 +240,8 @@ export const RecipientStepCard = ({
|
||||
);
|
||||
}}
|
||||
</Droppable>
|
||||
|
||||
{isLastStep && <RecipientStepGap droppableId="gap-end" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user