158 lines
4.7 KiB
Swift
158 lines
4.7 KiB
Swift
//
|
|
// ActionButton.swift
|
|
// CasinoKit
|
|
//
|
|
// Reusable action buttons for casino games (Deal, Hit, Stand, etc.).
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
/// A styled action button for casino games.
|
|
public struct ActionButton: View {
|
|
/// The button title.
|
|
public let title: String
|
|
|
|
/// Optional SF Symbol icon.
|
|
public let icon: String?
|
|
|
|
/// Button style variant.
|
|
public let style: ActionButtonStyle
|
|
|
|
/// Whether the button is enabled.
|
|
public let isEnabled: Bool
|
|
|
|
/// The action to perform.
|
|
public let action: () -> Void
|
|
|
|
// Font sizes
|
|
private let fontSize: CGFloat = 18
|
|
private let iconSize: CGFloat = 20
|
|
|
|
/// Minimum button width to prevent tiny buttons for short words like "Hit"
|
|
private let minButtonWidth: CGFloat = CasinoDesign.Size.actionButtonMinWidth
|
|
|
|
/// Creates an action button.
|
|
/// - Parameters:
|
|
/// - title: The button title.
|
|
/// - icon: Optional SF Symbol name.
|
|
/// - style: The button style.
|
|
/// - isEnabled: Whether enabled.
|
|
/// - action: The action to perform.
|
|
public init(
|
|
_ title: String,
|
|
icon: String? = nil,
|
|
style: ActionButtonStyle = .primary,
|
|
isEnabled: Bool = true,
|
|
action: @escaping () -> Void
|
|
) {
|
|
self.title = title
|
|
self.icon = icon
|
|
self.style = style
|
|
self.isEnabled = isEnabled
|
|
self.action = action
|
|
}
|
|
|
|
public var body: some View {
|
|
Button(action: action) {
|
|
HStack(spacing: CasinoDesign.Spacing.small) {
|
|
if let icon = icon {
|
|
Image(systemName: icon)
|
|
.font(.system(size: iconSize, weight: .semibold))
|
|
}
|
|
Text(title)
|
|
.font(.system(size: fontSize, weight: .bold))
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(CasinoDesign.MinScaleFactor.relaxed)
|
|
}
|
|
.foregroundStyle(style.foregroundColor)
|
|
.frame(minWidth: minButtonWidth)
|
|
.padding(.horizontal, CasinoDesign.Spacing.xLarge)
|
|
.padding(.vertical, CasinoDesign.Spacing.medium)
|
|
.background(style.background)
|
|
.shadow(color: style.shadowColor, radius: CasinoDesign.Shadow.radiusMedium)
|
|
}
|
|
.disabled(!isEnabled)
|
|
.opacity(isEnabled ? 1.0 : CasinoDesign.Opacity.medium)
|
|
.accessibilityLabel(title)
|
|
}
|
|
}
|
|
|
|
/// Style variants for action buttons.
|
|
public enum ActionButtonStyle {
|
|
/// Primary gold button (Deal, Hit, etc.)
|
|
case primary
|
|
/// Destructive red button (Clear, Fold, etc.)
|
|
case destructive
|
|
/// Secondary subtle button
|
|
case secondary
|
|
/// Custom color button for game-specific actions
|
|
case custom(Color)
|
|
|
|
var foregroundColor: Color {
|
|
switch self {
|
|
case .primary: return .black
|
|
case .destructive, .secondary, .custom: return .white
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
var background: some View {
|
|
switch self {
|
|
case .primary:
|
|
Capsule()
|
|
.fill(
|
|
LinearGradient(
|
|
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
)
|
|
case .destructive:
|
|
Capsule()
|
|
.fill(Color.CasinoButton.destructive)
|
|
case .secondary:
|
|
Capsule()
|
|
.fill(Color.white.opacity(CasinoDesign.Opacity.hint))
|
|
case .custom(let color):
|
|
Capsule()
|
|
.fill(color)
|
|
}
|
|
}
|
|
|
|
var shadowColor: Color {
|
|
switch self {
|
|
case .primary: return .yellow.opacity(CasinoDesign.Opacity.light)
|
|
case .destructive: return .red.opacity(CasinoDesign.Opacity.light)
|
|
case .secondary, .custom: return .clear
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ZStack {
|
|
Color.CasinoTable.felt.ignoresSafeArea()
|
|
|
|
VStack(spacing: 20) {
|
|
ActionButton("Deal", icon: "play.fill", style: .primary) { }
|
|
ActionButton("Clear", icon: "xmark.circle", style: .destructive) { }
|
|
ActionButton("Stand", style: .secondary) { }
|
|
ActionButton("Hit", style: .custom(.green)) { }
|
|
ActionButton("Disabled", style: .primary, isEnabled: false) { }
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview("Casino Action Buttons") {
|
|
ZStack {
|
|
Color.CasinoTable.felt.ignoresSafeArea()
|
|
|
|
HStack(spacing: CasinoDesign.Spacing.medium) {
|
|
ActionButton("Hit", style: .custom(.green)) { }
|
|
ActionButton("Stand", style: .secondary) { }
|
|
ActionButton("Double", style: .custom(Color.purple)) {}
|
|
ActionButton("Split", style: .custom(Color.yellow)) {}
|
|
}
|
|
}
|
|
}
|
|
|