import SwiftUI /// The **shadow placeholder** — the outline occupying an item's proposed landing spot /// (DRAG-REORDER.md § The pieces). /// /// One shape for both the drag's shadows and the lane resize's resting footprint, because they are /// the same thing said twice: "here is the space this item occupies in the layout". They differ only /// in whether the space is a *proposal* — the drag's is dashed, because a dashed outline is what /// 03-board-ui.md's placeholder vocabulary asks for and what makes "the drop lands exactly here" /// legible; the resize's is a plain fill, because the lane is already there. /// /// **Hit-transparent, always.** The strip's own drop target has to stay live beneath the shadows /// (DRAG-REORDER.md § Single-target dispatch: "shadow placeholders are hit-transparent, so the strip /// target stays live beneath them"), and a shadow that swallowed the release would strand the drop. /// /// **A drag's shadow lives exactly as long as the drag does.** The moment the mouse comes up the /// slot it was holding draws the dropped card itself (`LaneView`'s `runSlots`, `DroppedCardFace`) — /// "a lingering shadow over a hidden card is the hold failing its one job" (03-board-ui.md § Motion, /// sharpened 2026-07-28). The one shadow that outlives a release is the cross-board *lane* arrival's, /// which has no column to draw until the echo lands (`BoardView.runSlots`). struct DragShadow: View { /// Matched to the surface it stands in for — a lane's plate is 10, a card's is 8. var cornerRadius: CGFloat = 10 /// A drop proposal (dashed) or a resting footprint (plain). var dashed: Bool = true var body: some View { RoundedRectangle(cornerRadius: cornerRadius) .fill(.quaternary.opacity(0.5)) .overlay { if dashed { RoundedRectangle(cornerRadius: cornerRadius) .strokeBorder( Color.accentColor.opacity(0.55), style: StrokeStyle(lineWidth: 1.5, dash: [6]) ) } } .allowsHitTesting(false) .accessibilityHidden(true) } } /// The count badge a multi-drag's replica wears — Finder's own affordance for "this many are coming /// with me" (04-interactions.md ▸ Drag and drop: "dragging any member of a multi-selection drags the /// whole selection"). /// /// Draws nothing for a single item, so every replica can carry it unconditionally. struct DragCountBadge: View { let count: Int var body: some View { if count > 1 { Text("\(count)") .font(.caption2.bold()) .monospacedDigit() .foregroundStyle(.white) .padding(.horizontal, 6) .padding(.vertical, 3) .background(Circle().fill(Color.accentColor)) .offset(x: 10, y: -10) } } }