import SwiftUI struct PrimaryActionStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { PrimaryActionBody(configuration: configuration) } private struct PrimaryActionBody: View { let configuration: Configuration @Environment(\.isEnabled) private var isEnabled var body: some View { configuration.label .font(.subheadline.weight(.semibold)) .frame(maxWidth: .infinity) .padding(.horizontal, 18) .frame(height: 44) .foregroundStyle(Color.idpPrimaryForeground) .background( RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous) .fill(isEnabled ? Color.idpPrimary : Color.idpMuted) ) .opacity(configuration.isPressed ? 0.9 : 1) .scaleEffect(configuration.isPressed ? 0.99 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) } } } struct SecondaryActionStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.subheadline.weight(.semibold)) .frame(maxWidth: .infinity) .padding(.horizontal, 18) .frame(height: 44) .foregroundStyle(.primary) .background( RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous) .fill(Color.clear) ) .overlay( RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous) .stroke(Color.idpBorder, lineWidth: 1) ) .opacity(configuration.isPressed ? 0.85 : 1) .scaleEffect(configuration.isPressed ? 0.99 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) } } struct DestructiveStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.subheadline.weight(.semibold)) .frame(maxWidth: .infinity) .padding(.horizontal, 18) .frame(height: 44) .foregroundStyle(.white) .background( RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous) .fill(Color.idpDestructive) ) .opacity(configuration.isPressed ? 0.9 : 1) .scaleEffect(configuration.isPressed ? 0.99 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) } } struct AccentActionStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.subheadline.weight(.semibold)) .frame(maxWidth: .infinity) .padding(.horizontal, 18) .frame(height: 44) .foregroundStyle(.white) .background( RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous) .fill(IdP.tint) ) .opacity(configuration.isPressed ? 0.9 : 1) .scaleEffect(configuration.isPressed ? 0.99 : 1) .animation(.easeOut(duration: 0.12), value: configuration.isPressed) } } struct GhostActionStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.subheadline.weight(.medium)) .padding(.horizontal, 12) .frame(height: 32) .foregroundStyle(.primary) .background( RoundedRectangle(cornerRadius: IdP.controlRadius, style: .continuous) .fill(configuration.isPressed ? Color.idpMuted : Color.clear) ) } } struct ShadcnBadge: View { enum Tone { case neutral case outline case ok case warn case danger case accent } let title: String var tone: Tone = .neutral var leading: Image? = nil var body: some View { HStack(spacing: 4) { if let leading { leading .font(.caption2.weight(.semibold)) } Text(title) } .font(.caption2.weight(.semibold)) .padding(.horizontal, 8) .padding(.vertical, 3) .foregroundStyle(foreground) .background(background, in: Capsule(style: .continuous)) .overlay( Capsule(style: .continuous) .stroke(strokeColor, lineWidth: strokeWidth) ) } private var foreground: Color { switch tone { case .neutral: return Color.idpMutedForeground case .outline: return .primary case .ok: return Color.idpOK case .warn: return Color(red: 0.52, green: 0.30, blue: 0.05) case .danger: return Color(red: 0.60, green: 0.10, blue: 0.10) case .accent: return IdP.tint } } private var background: Color { switch tone { case .neutral: return Color.idpMuted case .outline: return .clear case .ok: return Color.idpOK.opacity(0.12) case .warn: return Color.idpWarn.opacity(0.18) case .danger: return Color.idpDestructive.opacity(0.14) case .accent: return Color.idpAccentSoft } } private var strokeColor: Color { tone == .outline ? Color.idpBorder : .clear } private var strokeWidth: CGFloat { tone == .outline ? 1 : 0 } }