Skip to content

Instantly share code, notes, and snippets.

@luthviar
Last active September 26, 2025 03:12
Show Gist options
  • Select an option

  • Save luthviar/0585a05951831bb6aa68079291e829dd to your computer and use it in GitHub Desktop.

Select an option

Save luthviar/0585a05951831bb6aa68079291e829dd to your computer and use it in GitHub Desktop.
import SwiftUI
struct ShippingInfoCardV2: View {
/// Sample data (replace with your real bindings/models)
let recipientName: String
let phoneNumber: String
let shippingAddress: String
/// UI states (UI-only interactions)
@State private var isAddressExpanded: Bool = false
@State private var showCopiedPhone: Bool = false
@State private var showCopiedAddress: Bool = false
@State private var pressedRow: Int? = nil
var body: some View {
/// Card container
VStack(alignment: .leading, spacing: 0) {
row(
index: 0,
title: "Nama Penerima",
content: {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(recipientName)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.primary)
.accessibilityLabel("Nama penerima")
.accessibilityValue(recipientName)
}
},
trailingAccessory: { EmptyView() }
)
Divider().opacity(0.06)
row(
index: 1,
title: "Nomor HP",
content: {
Text(phoneNumber)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.primary)
.textSelection(.enabled)
.accessibilityLabel("Nomor HP")
.accessibilityValue(phoneNumber)
},
trailingAccessory: {
EmptyView()
}
)
.overlay(alignment: .trailing) {
if showCopiedPhone {
copiedBadge
.transition(.opacity.combined(with: .move(edge: .trailing)))
.padding(.trailing, 8)
}
}
Divider().opacity(0.06)
row(
index: 2,
title: "Alamat Pengiriman",
content: {
VStack(alignment: .leading, spacing: 6) {
Text(shippingAddress)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.primary)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.textSelection(.enabled)
.accessibilityLabel("Alamat Pengiriman")
.accessibilityValue(shippingAddress)
}
},
trailingAccessory: {
EmptyView()
}
)
.overlay(alignment: .trailing) { // same note as above
if showCopiedAddress {
copiedBadge
.transition(.opacity.combined(with: .move(edge: .trailing)))
.padding(.trailing, 8)
}
}
}
.padding(12)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color(.systemBackground))
)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(Color.black.opacity(0.08), lineWidth: 1)
)
.shadow(color: Color.black.opacity(0.04), radius: 8, x: 0, y: 2)
.padding(.horizontal, 16)
.padding(.vertical, 8)
}
// MARK: - Subviews
@ViewBuilder
private func row<Content: View, Accessory: View>(
index: Int,
title: String,
@ViewBuilder content: () -> Content,
@ViewBuilder trailingAccessory: () -> Accessory
) -> some View {
HStack(alignment: .top, spacing: 16) {
Text(title)
.font(.system(size: 12, weight: .regular))
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityAddTraits(.isStaticText)
HStack(alignment: .top, spacing: 8) {
content()
.frame(maxWidth: .infinity, alignment: .leading)
trailingAccessory()
}
}
.padding(.vertical, 8)
.contentShape(Rectangle())
.background(pressedRow == index ? Color.black.opacity(0.04) : Color.clear)
.onLongPressGesture(minimumDuration: 0.01, pressing: { pressing in
withAnimation(.easeInOut(duration: 0.12)) {
pressedRow = pressing ? index : nil
}
}, perform: { })
}
private var copiedBadge: some View {
HStack(spacing: 6) {
Image(systemName: "checkmark.circle.fill")
Text("Disalin")
}
.font(.footnote.weight(.semibold))
.foregroundColor(.green)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(
Capsule(style: .continuous)
.fill(Color.green.opacity(0.12))
)
.accessibilityHidden(true)
}
@ViewBuilder
private func copyButton(action: @escaping () -> Void) -> some View {
Button(action: action) {
Image(systemName: "doc.on.doc")
.font(.title3)
}
.buttonStyle(.plain)
.tint(.secondary)
.accessibilityLabel("Salin")
}
// MARK: - Helpers
// FIX: use Binding instead of inout so we can update it from an escaping closure safely
private func showCopiedTemporarily(_ flag: Binding<Bool>) {
withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) {
flag.wrappedValue = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.4) {
withAnimation(.easeOut(duration: 0.2)) {
flag.wrappedValue = false
}
}
}
}
struct ShippingInfoCourierCardV2: View {
/// Sample data (replace with your real bindings/models)
let shippingType: String
let trackingNumber: String
/// UI states (UI-only interactions)
@State private var isAddressExpanded: Bool = false
@State private var showCopiedPhone: Bool = false
@State private var showCopiedAddress: Bool = false
@State private var pressedRow: Int? = nil
var body: some View {
/// Card container
VStack(alignment: .leading, spacing: 0) {
row(
index: 0,
title: "Jenis Pengiriman",
textToCopy: "",
content: {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(shippingType)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.primary)
.accessibilityLabel("Nama penerima")
.accessibilityValue(shippingType)
}
},
trailingAccessory: { EmptyView() }
)
Divider().opacity(0.06)
row(
index: 1,
title: "Nomor Resi",
textToCopy: trackingNumber,
content: {
Text(trackingNumber)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.primary)
.textSelection(.enabled)
.accessibilityLabel("Nomor HP")
.accessibilityValue(trackingNumber)
},
trailingAccessory: {
EmptyView()
}
)
.overlay(alignment: .trailing) { // if you used .trailingLastTextBaseline before, .trailing is safer
}
}
.padding(12)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color(.systemBackground))
)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(Color.black.opacity(0.08), lineWidth: 1)
)
.shadow(color: Color.black.opacity(0.04), radius: 8, x: 0, y: 2)
.padding(.horizontal, 16)
.padding(.vertical, 8)
}
// MARK: - Subviews
@ViewBuilder
private func row<Content: View, Accessory: View>(
index: Int,
title: String,
textToCopy: String = "",
@ViewBuilder content: () -> Content,
@ViewBuilder trailingAccessory: () -> Accessory
) -> some View {
HStack(alignment: .top, spacing: 16) {
HStack(alignment: .center, spacing: 8) {
Text(title)
.font(.system(size: 12, weight: .regular))
.foregroundColor(.secondary)
if textToCopy.isEmpty == false {
Button {
UIPasteboard.general.string = textToCopy
UIImpactFeedbackGenerator(style: .light).impactOccurred()
CUISimpleToast.show("Berhasil disalin: \(textToCopy)")
} label: {
Image(systemName: "doc.on.doc")
.resizable()
.scaledToFit()
.frame(width: 12.6)
.symbolRenderingMode(.hierarchical)
}
.buttonStyle(.plain)
.foregroundColor(.primary)
.opacity(1)
.accessibilityLabel("Salin textToCopy")
.accessibilityHint("Menyalin textToCopy ke clipboard")
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityAddTraits(.isStaticText)
HStack(alignment: .top, spacing: 8) {
content()
.frame(maxWidth: .infinity, alignment: .leading)
trailingAccessory()
}
}
.padding(.vertical, 8)
.contentShape(Rectangle())
.background(pressedRow == index ? Color.black.opacity(0.04) : Color.clear)
.onLongPressGesture(minimumDuration: 0.01, pressing: { pressing in
withAnimation(.easeInOut(duration: 0.12)) {
pressedRow = pressing ? index : nil
}
}, perform: { })
}
}
/*
ScrollView {
ShippingInfoCardV2(
recipientName: "Budhi Aghanim",
phoneNumber: "+6282112345678",
shippingAddress: """
Jln. Alamat Raya No.1
RT.002 RW.003, Perumahan
Mutiara Gading, Dayeuh
Kolot, 17462
"""
)
.preferredColorScheme(.light)
VStack(spacing: 20) {
ShippingInfoCourierCardV2(
shippingType: "JNE Regular",
trackingNumber: "JNE1234567890ID"
)
ShippingInfoCourierCardV2(
shippingType: "JNE Regular",
trackingNumber: "JNE1234567890ID"
)
}
}
*/
@luthviar
Copy link
Author

IMG_CE6D6B801112-1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment