Refine primary and toolbar actions, align row buttons, normalize status surfaces, and split storage from destructive cleanup. Verified with Swift tests and the simulator build.
140 lines
4.0 KiB
Swift
140 lines
4.0 KiB
Swift
import SwiftUI
|
|
|
|
public struct AIButtonStyle: ButtonStyle {
|
|
public static let pressedScale: CGFloat = 0.97
|
|
|
|
public enum Kind: Hashable {
|
|
case primary
|
|
case secondary
|
|
case danger
|
|
|
|
var foregroundColor: Color { foregroundColor(isEnabled: true) }
|
|
|
|
var backgroundColor: Color { backgroundColor(isEnabled: true) }
|
|
|
|
func foregroundColor(isEnabled: Bool) -> Color {
|
|
switch self {
|
|
case .primary: return isEnabled ? AITheme.onPrimary : AITheme.muted
|
|
case .danger: return isEnabled ? AITheme.onDanger : AITheme.muted
|
|
case .secondary: return isEnabled ? AITheme.ink : AITheme.muted
|
|
}
|
|
}
|
|
|
|
func backgroundColor(isEnabled: Bool) -> Color {
|
|
switch self {
|
|
case .primary, .secondary, .danger:
|
|
return isEnabled ? kindBackground : AITheme.control
|
|
}
|
|
}
|
|
|
|
private var kindBackground: Color {
|
|
switch self {
|
|
case .primary: return AITheme.primary
|
|
case .secondary: return AITheme.control
|
|
case .danger: return AITheme.danger
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum Size: Hashable {
|
|
case compact
|
|
case regular
|
|
case large
|
|
case toolbar
|
|
case toolbarInline
|
|
}
|
|
|
|
private let kind: Kind
|
|
private let size: Size
|
|
private let fixedWidthRequest: CGFloat?
|
|
let fillsWidth: Bool
|
|
@Environment(\.isEnabled) private var isEnabled
|
|
|
|
public init(
|
|
kind: Kind = .secondary,
|
|
size: Size = .regular,
|
|
fixedWidth: CGFloat? = nil,
|
|
fillsWidth: Bool = false
|
|
) {
|
|
self.kind = kind
|
|
self.size = size
|
|
self.fixedWidthRequest = fixedWidth
|
|
self.fillsWidth = fillsWidth
|
|
}
|
|
|
|
public func makeBody(configuration: Configuration) -> some View {
|
|
let height: CGFloat =
|
|
switch size {
|
|
case .compact: AITheme.compactButtonHeight
|
|
case .large: AITheme.largeButtonHeight
|
|
case .regular: AITheme.buttonHeight
|
|
case .toolbar: AITheme.toolbarButtonHeight
|
|
case .toolbarInline: AITheme.toolbarButtonHeight
|
|
}
|
|
|
|
configuration.label
|
|
.labelStyle(.titleAndIcon)
|
|
.font(
|
|
.system(size: size == .compact || size == .toolbarInline ? 15 : 16, weight: .semibold)
|
|
)
|
|
.lineLimit(1)
|
|
.foregroundStyle(kind.foregroundColor(isEnabled: isEnabled))
|
|
.padding(.horizontal, fixedWidth == nil ? (kind == .secondary ? 12 : 14) : 0)
|
|
.frame(
|
|
minWidth: (!fillsWidth && fixedWidth == nil) ? minimumWidth : 0,
|
|
minHeight: height
|
|
)
|
|
.frame(maxWidth: fillsWidth ? .infinity : nil)
|
|
.frame(width: fixedWidth)
|
|
.background(
|
|
kind == .primary && isEnabled && size != .toolbarInline
|
|
? AnyShapeStyle(
|
|
LinearGradient(
|
|
colors: [AITheme.primaryTop, AITheme.primaryBottom],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
)
|
|
: AnyShapeStyle(kind.backgroundColor(isEnabled: isEnabled))
|
|
)
|
|
.shadow(
|
|
color: (kind == .secondary || !isEnabled) ? .clear : .black.opacity(0.10),
|
|
radius: 12,
|
|
x: 0,
|
|
y: 5
|
|
)
|
|
.clipShape(
|
|
RoundedRectangle(
|
|
cornerRadius: size == .toolbarInline ? AITheme.toolbarButtonHeight / 2 : AITheme.buttonRadius,
|
|
style: .continuous
|
|
)
|
|
)
|
|
.overlay {
|
|
RoundedRectangle(
|
|
cornerRadius: size == .toolbarInline ? AITheme.toolbarButtonHeight / 2 : AITheme.buttonRadius,
|
|
style: .continuous
|
|
)
|
|
.strokeBorder(
|
|
size == .toolbarInline && kind == .primary
|
|
? AITheme.primary.opacity(0.28)
|
|
: (kind == .secondary || !isEnabled ? AITheme.line : .clear)
|
|
)
|
|
}
|
|
.scaleEffect(configuration.isPressed ? Self.pressedScale : 1)
|
|
.opacity(configuration.isPressed ? 0.82 : 1)
|
|
}
|
|
|
|
private var minimumWidth: CGFloat {
|
|
switch size {
|
|
case .compact: return AITheme.compactButtonMinWidth
|
|
case .regular: return AITheme.regularButtonMinWidth
|
|
case .large: return 92
|
|
case .toolbar, .toolbarInline: return AITheme.toolbarButtonMinWidth
|
|
}
|
|
}
|
|
|
|
private var fixedWidth: CGFloat? {
|
|
fixedWidthRequest ?? (size == .toolbar ? AITheme.toolbarButtonWidth : nil)
|
|
}
|
|
}
|