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. struct DragShadow: View { /// Matched to the surface it stands in for — the lane's plate radius or the card's, both /// font-derived (`BoardMetrics`) and both supplied by the caller, which is the one that knows /// which surface this shadow is standing in for. let cornerRadius: CGFloat /// A drop proposal (dashed) or a resting footprint (plain). var dashed: Bool = true /// Increase Contrast and Reduce Transparency, for the outline and the fill below /// (10-accessibility.md; `Accommodations`). A shadow's whole job is to say "here", and both /// halves of how it says so are alpha-based by default. @Environment(\.colorSchemeContrast) private var contrast @Environment(\.accessibilityReduceTransparency) private var reduceTransparency var body: some View { RoundedRectangle(cornerRadius: cornerRadius) .fill(Accommodations.dragShadowWash(reduceTransparency: reduceTransparency).style) .overlay { if dashed { RoundedRectangle(cornerRadius: cornerRadius) .strokeBorder( Color.accentColor.opacity(Accommodations.accentOpacity(0.55, contrast: contrast)), style: StrokeStyle( lineWidth: Accommodations.borderWidth(1.5, contrast: contrast), 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 /// The badge is a disc around a numeral, so every figure in it follows the numeral's font /// (`BoardMetrics`, 10-accessibility.md's full-relative-scaling rule) — at the standard body size /// they are the 6, 3 and 10 points it has always drawn. private var pointSize: CGFloat { BoardMetrics.bodyPointSize } var body: some View { if count > 1 { Text("\(count)") .font(.caption2.bold()) .monospacedDigit() .foregroundStyle(.white) .padding(.horizontal, BoardMetrics.em(0.45, bodyPointSize: pointSize)) .padding(.vertical, BoardMetrics.em(0.25, bodyPointSize: pointSize)) .background(Circle().fill(Color.accentColor)) .offset( x: BoardMetrics.em(0.75, bodyPointSize: pointSize), y: -BoardMetrics.em(0.75, bodyPointSize: pointSize) ) } } }