271d9657bf
CI / test (push) Has been cancelled
Tighten the inbox, detail, and watch layouts so approval actions feel denser and more direct across compact surfaces.
165 lines
5.5 KiB
Swift
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
|
|
}
|
|
}
|