feat(10-01): update handleDragEnd and renderFields for typed field creation

- handleDragEnd: determine droppedType from active.id with validTypes set guard
- handleDragEnd: checkbox fields drop at 24x24pt; all other types drop at 144x36pt
- handleDragEnd: newField now includes type property set to droppedType
- renderFields: use getFieldType() + PALETTE_TOKENS lookup to get per-field color and label
- renderFields: border, background, and text color are now driven by fieldColor
- renderFields: resize handle corners use fieldColor instead of hardcoded blue
- renderFields: span displays fieldLabel instead of hardcoded 'Signature'
This commit is contained in:
Chandler Copeland
2026-03-21 12:50:18 -06:00
parent 4140c220b1
commit 1e92ca363a

View File

@@ -254,9 +254,20 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly =
const rawX = ghostRect.left - refRect.left; const rawX = ghostRect.left - refRect.left;
const rawY = ghostRect.top - refRect.top; const rawY = ghostRect.top - refRect.top;
// Determine the field type from the dnd-kit active.id (token id IS the SignatureFieldType)
const validTypes = new Set<string>(['client-signature', 'initials', 'text', 'checkbox', 'date', 'agent-signature']);
const droppedType: SignatureFieldType = validTypes.has(active.id as string)
? (active.id as SignatureFieldType)
: 'client-signature';
// Checkbox fields are square (24x24pt). All other types: 144x36pt.
const isCheckbox = droppedType === 'checkbox';
const fieldW = isCheckbox ? 24 : 144;
const fieldH = isCheckbox ? 24 : 36;
// Field dimensions in screen pixels (for clamping) // Field dimensions in screen pixels (for clamping)
const fieldWpx = (144 / pageInfo.originalWidth) * renderedW; const fieldWpx = (fieldW / pageInfo.originalWidth) * renderedW;
const fieldHpx = (36 / pageInfo.originalHeight) * renderedH; const fieldHpx = (fieldH / pageInfo.originalHeight) * renderedH;
// Clamp so field stays within canvas bounds // Clamp so field stays within canvas bounds
const clampedX = Math.max(0, Math.min(rawX, renderedW - fieldWpx)); const clampedX = Math.max(0, Math.min(rawX, renderedW - fieldWpx));
@@ -273,8 +284,9 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly =
page: currentPage, page: currentPage,
x: pdfX, x: pdfX,
y: pdfY, y: pdfY,
width: 144, width: fieldW,
height: 36, height: fieldH,
type: droppedType,
}; };
const next = [...fields, newField]; const next = [...fields, newField];
@@ -549,6 +561,12 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly =
const isMoving = activeDragFieldId === field.id && activeDragType === 'move'; const isMoving = activeDragFieldId === field.id && activeDragType === 'move';
// Per-type color and label
const fieldType = getFieldType(field);
const tokenMeta = PALETTE_TOKENS.find((t) => t.id === fieldType);
const fieldColor = tokenMeta?.color ?? '#2563eb';
const fieldLabel = tokenMeta?.label ?? 'Signature';
// Resize handle style factory // Resize handle style factory
const resizeHandle = (corner: ResizeCorner) => { const resizeHandle = (corner: ResizeCorner) => {
const cursors: Record<ResizeCorner, string> = { const cursors: Record<ResizeCorner, string> = {
@@ -573,7 +591,7 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly =
width: 10, width: 10,
height: 10, height: 10,
cursor: cursors[corner], cursor: cursors[corner],
background: '#2563eb', background: fieldColor,
zIndex: 12, zIndex: 12,
pointerEvents: readOnly ? 'none' : 'all', pointerEvents: readOnly ? 'none' : 'all',
}} }}
@@ -595,21 +613,21 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly =
top: top - heightPx + canvasOffset.y, // top is y of bottom-left corner; shift up by height top: top - heightPx + canvasOffset.y, // top is y of bottom-left corner; shift up by height
width: widthPx, width: widthPx,
height: heightPx, height: heightPx,
border: '2px solid #2563eb', border: `2px solid ${fieldColor}`,
background: readOnly ? 'rgba(37,99,235,0.05)' : 'rgba(37,99,235,0.1)', background: readOnly ? `${fieldColor}0d` : `${fieldColor}1a`,
borderRadius: '2px', borderRadius: '2px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
padding: '0 4px', padding: '0 4px',
fontSize: '10px', fontSize: '10px',
color: '#2563eb', color: fieldColor,
// Raise above droppable overlay so the delete button is clickable // Raise above droppable overlay so the delete button is clickable
zIndex: 10, zIndex: 10,
pointerEvents: readOnly ? 'none' : 'all', pointerEvents: readOnly ? 'none' : 'all',
boxSizing: 'border-box', boxSizing: 'border-box',
cursor: readOnly ? 'default' : (isMoving ? 'grabbing' : 'grab'), cursor: readOnly ? 'default' : (isMoving ? 'grabbing' : 'grab'),
boxShadow: isMoving ? '0 4px 12px rgba(37,99,235,0.35)' : undefined, boxShadow: isMoving ? `0 4px 12px ${fieldColor}59` : undefined,
userSelect: 'none', userSelect: 'none',
touchAction: 'none', touchAction: 'none',
opacity: readOnly ? 0.6 : 1, opacity: readOnly ? 0.6 : 1,
@@ -621,7 +639,7 @@ export function FieldPlacer({ docId, pageInfo, currentPage, children, readOnly =
handleMoveStart(e, field.id); handleMoveStart(e, field.id);
}} }}
> >
<span style={{ pointerEvents: 'none' }}>Signature</span> <span style={{ pointerEvents: 'none' }}>{fieldLabel}</span>
{!readOnly && ( {!readOnly && (
<button <button
data-no-move data-no-move