Files
swiftapp/swift/Sources/Core/Design/ButtonStyles.swift
T
jkunz 271d9657bf
CI / test (push) Has been cancelled
Refine inbox and watch approval presentation
Tighten the inbox, detail, and watch layouts so approval actions feel denser and more direct across compact surfaces.
2026-04-19 21:50:03 +02:00

165 lines
5.5 KiB
Swift

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
}
}