Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
4dda80a900
commit
058f6ab75e
@ -301,46 +301,12 @@ struct DeckCountPicker: View {
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
ForEach(DeckCount.allCases) { count in
|
||||
Button {
|
||||
selection = count
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(count.displayName)
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(count.description)
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if selection == count {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: Design.Size.checkmark))
|
||||
.foregroundStyle(.yellow)
|
||||
} else {
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
|
||||
.frame(width: Design.Size.checkmark, height: Design.Size.checkmark)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(selection == count ? Color.yellow.opacity(Design.Opacity.subtle) : Color.clear)
|
||||
SelectableRow(
|
||||
title: count.displayName,
|
||||
subtitle: count.description,
|
||||
isSelected: selection == count,
|
||||
action: { selection = count }
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
selection == count ? Color.yellow.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
|
||||
lineWidth: Design.LineWidth.thin
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -447,57 +413,13 @@ struct TableLimitsPicker: View {
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
ForEach(TableLimits.allCases) { limit in
|
||||
Button {
|
||||
selection = limit
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(limit.displayName)
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(limit.detailedDescription)
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Limits badge
|
||||
Text(limit.description)
|
||||
.font(.system(size: Design.BaseFontSize.body, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(selection == limit ? .black : .yellow)
|
||||
.padding(.horizontal, Design.Spacing.small)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(selection == limit ? Color.yellow : Color.yellow.opacity(Design.Opacity.hint))
|
||||
SelectableRow(
|
||||
title: limit.displayName,
|
||||
subtitle: limit.detailedDescription,
|
||||
isSelected: selection == limit,
|
||||
badge: { BadgePill(text: limit.description, isSelected: selection == limit) },
|
||||
action: { selection = limit }
|
||||
)
|
||||
|
||||
if selection == limit {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: Design.Size.checkmark - 2))
|
||||
.foregroundStyle(.yellow)
|
||||
} else {
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
|
||||
.frame(width: Design.Size.checkmark - 2, height: Design.Size.checkmark - 2)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(selection == limit ? Color.yellow.opacity(Design.Opacity.subtle) : Color.clear)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
selection == limit ? Color.yellow.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
|
||||
lineWidth: Design.LineWidth.thin
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,47 +166,13 @@ struct GameStylePicker: View {
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
ForEach(BlackjackStyle.allCases) { style in
|
||||
Button {
|
||||
selection = style
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(style.displayName)
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(style.description)
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if selection == style {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: Design.Size.checkmark))
|
||||
.foregroundStyle(Color.Settings.accent)
|
||||
} else {
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
|
||||
.frame(width: Design.Size.checkmark, height: Design.Size.checkmark)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(selection == style ? Color.Settings.accent.opacity(Design.Opacity.subtle) : Color.clear)
|
||||
SelectableRow(
|
||||
title: style.displayName,
|
||||
subtitle: style.description,
|
||||
isSelected: selection == style,
|
||||
accentColor: Color.Settings.accent,
|
||||
action: { selection = style }
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
selection == style ? Color.Settings.accent.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
|
||||
lineWidth: Design.LineWidth.thin
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,46 +186,13 @@ struct DeckCountPicker: View {
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
ForEach(DeckCount.allCases) { count in
|
||||
Button {
|
||||
selection = count
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(count.displayName)
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(count.description)
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if selection == count {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: Design.Size.checkmark))
|
||||
.foregroundStyle(Color.Settings.accent)
|
||||
} else {
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
|
||||
.frame(width: Design.Size.checkmark, height: Design.Size.checkmark)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(selection == count ? Color.Settings.accent.opacity(Design.Opacity.subtle) : Color.clear)
|
||||
SelectableRow(
|
||||
title: count.displayName,
|
||||
subtitle: count.description,
|
||||
isSelected: selection == count,
|
||||
accentColor: Color.Settings.accent,
|
||||
action: { selection = count }
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
selection == count ? Color.Settings.accent.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
|
||||
lineWidth: Design.LineWidth.thin
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -273,57 +206,14 @@ struct TableLimitsPicker: View {
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
ForEach(TableLimits.allCases) { limit in
|
||||
Button {
|
||||
selection = limit
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(limit.displayName)
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(limit.detailedDescription)
|
||||
.font(.system(size: Design.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Limits badge pill
|
||||
Text(limit.description)
|
||||
.font(.system(size: Design.BaseFontSize.body, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(selection == limit ? .black : Color.Settings.accent)
|
||||
.padding(.horizontal, Design.Spacing.small)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(selection == limit ? Color.Settings.accent : Color.Settings.accent.opacity(Design.Opacity.hint))
|
||||
SelectableRow(
|
||||
title: limit.displayName,
|
||||
subtitle: limit.detailedDescription,
|
||||
isSelected: selection == limit,
|
||||
accentColor: Color.Settings.accent,
|
||||
badge: { BadgePill(text: limit.description, isSelected: selection == limit, accentColor: Color.Settings.accent) },
|
||||
action: { selection = limit }
|
||||
)
|
||||
|
||||
if selection == limit {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: Design.Size.checkmark - 2))
|
||||
.foregroundStyle(Color.Settings.accent)
|
||||
} else {
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
|
||||
.frame(width: Design.Size.checkmark - 2, height: Design.Size.checkmark - 2)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(selection == limit ? Color.Settings.accent.opacity(Design.Opacity.subtle) : Color.clear)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
selection == limit ? Color.Settings.accent.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
|
||||
lineWidth: Design.LineWidth.thin
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +52,9 @@
|
||||
// - SpeedPicker
|
||||
// - VolumePicker
|
||||
// - BalancePicker
|
||||
// - SelectableRow (card-like selectable picker row)
|
||||
// - SelectionIndicator (checkmark circle)
|
||||
// - BadgePill (capsule badge for values)
|
||||
|
||||
// MARK: - Branding
|
||||
// - AppIconView, AppIconConfig
|
||||
|
||||
@ -140,6 +140,188 @@ public struct VolumePicker: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Selectable Row
|
||||
|
||||
/// A card-like selectable row with title, subtitle, optional badge, and selection indicator.
|
||||
/// Use this for settings pickers like table limits, deck count, game style, etc.
|
||||
public struct SelectableRow<Badge: View>: View {
|
||||
/// The main title text.
|
||||
public let title: String
|
||||
|
||||
/// The subtitle/description text.
|
||||
public let subtitle: String
|
||||
|
||||
/// Whether this row is currently selected.
|
||||
public let isSelected: Bool
|
||||
|
||||
/// Optional badge view (e.g., a pill showing "$10 - $1,000").
|
||||
public let badge: Badge?
|
||||
|
||||
/// The accent color for selection highlighting.
|
||||
public let accentColor: Color
|
||||
|
||||
/// Action when tapped.
|
||||
public let action: () -> Void
|
||||
|
||||
/// Creates a selectable row.
|
||||
/// - Parameters:
|
||||
/// - title: The main title.
|
||||
/// - subtitle: The subtitle description.
|
||||
/// - isSelected: Whether this row is selected.
|
||||
/// - accentColor: Color for selection (default: yellow).
|
||||
/// - badge: Optional badge view.
|
||||
/// - action: Action when tapped.
|
||||
public init(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
isSelected: Bool,
|
||||
accentColor: Color = .yellow,
|
||||
@ViewBuilder badge: () -> Badge? = { nil as EmptyView? },
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.isSelected = isSelected
|
||||
self.accentColor = accentColor
|
||||
self.badge = badge()
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Button(action: action) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: CasinoDesign.Spacing.xxSmall) {
|
||||
Text(title)
|
||||
.font(.system(size: CasinoDesign.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(subtitle)
|
||||
.font(.system(size: CasinoDesign.BaseFontSize.body))
|
||||
.foregroundStyle(.white.opacity(CasinoDesign.Opacity.medium))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if let badge = badge {
|
||||
badge
|
||||
}
|
||||
|
||||
SelectionIndicator(isSelected: isSelected, accentColor: accentColor)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: CasinoDesign.CornerRadius.medium)
|
||||
.fill(isSelected ? accentColor.opacity(CasinoDesign.Opacity.subtle) : Color.clear)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: CasinoDesign.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
isSelected ? accentColor.opacity(CasinoDesign.Opacity.medium) : Color.white.opacity(CasinoDesign.Opacity.subtle),
|
||||
lineWidth: CasinoDesign.LineWidth.thin
|
||||
)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience initializer for rows without a badge
|
||||
extension SelectableRow where Badge == EmptyView {
|
||||
/// Creates a selectable row without a badge.
|
||||
public init(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
isSelected: Bool,
|
||||
accentColor: Color = .yellow,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.isSelected = isSelected
|
||||
self.accentColor = accentColor
|
||||
self.badge = nil
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Selection Indicator
|
||||
|
||||
/// A circle indicator that shows selected (checkmark) or unselected (outline) state.
|
||||
public struct SelectionIndicator: View {
|
||||
/// Whether the item is selected.
|
||||
public let isSelected: Bool
|
||||
|
||||
/// The accent color for the checkmark.
|
||||
public let accentColor: Color
|
||||
|
||||
/// The size of the indicator.
|
||||
public let size: CGFloat
|
||||
|
||||
/// Creates a selection indicator.
|
||||
/// - Parameters:
|
||||
/// - isSelected: Whether selected.
|
||||
/// - accentColor: Color for checkmark (default: yellow).
|
||||
/// - size: Size of the indicator (default: checkmark size from design).
|
||||
public init(
|
||||
isSelected: Bool,
|
||||
accentColor: Color = .yellow,
|
||||
size: CGFloat = CasinoDesign.Size.checkmark
|
||||
) {
|
||||
self.isSelected = isSelected
|
||||
self.accentColor = accentColor
|
||||
self.size = size
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: size))
|
||||
.foregroundStyle(accentColor)
|
||||
} else {
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(CasinoDesign.Opacity.light), lineWidth: CasinoDesign.LineWidth.medium)
|
||||
.frame(width: size, height: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Badge Pill
|
||||
|
||||
/// A capsule-shaped badge for displaying values like "$10 - $1,000".
|
||||
public struct BadgePill: View {
|
||||
/// The text to display in the badge.
|
||||
public let text: String
|
||||
|
||||
/// Whether the parent row is selected.
|
||||
public let isSelected: Bool
|
||||
|
||||
/// The accent color.
|
||||
public let accentColor: Color
|
||||
|
||||
/// Creates a badge pill.
|
||||
/// - Parameters:
|
||||
/// - text: The badge text.
|
||||
/// - isSelected: Whether the parent row is selected.
|
||||
/// - accentColor: Color for the badge (default: yellow).
|
||||
public init(text: String, isSelected: Bool, accentColor: Color = .yellow) {
|
||||
self.text = text
|
||||
self.isSelected = isSelected
|
||||
self.accentColor = accentColor
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Text(text)
|
||||
.font(.system(size: CasinoDesign.BaseFontSize.body, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(isSelected ? .black : accentColor)
|
||||
.padding(.horizontal, CasinoDesign.Spacing.small)
|
||||
.padding(.vertical, CasinoDesign.Spacing.xSmall)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(isSelected ? accentColor : accentColor.opacity(CasinoDesign.Opacity.hint))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Balance Picker
|
||||
|
||||
/// A grid picker for selecting a starting balance.
|
||||
@ -198,6 +380,34 @@ public struct BalancePicker: View {
|
||||
#Preview {
|
||||
ScrollView {
|
||||
VStack(spacing: 20) {
|
||||
// Selectable rows
|
||||
VStack(spacing: CasinoDesign.Spacing.small) {
|
||||
SelectableRow(
|
||||
title: "Low Stakes",
|
||||
subtitle: "Standard mini table",
|
||||
isSelected: true,
|
||||
badge: { BadgePill(text: "$10 - $1,000", isSelected: true) },
|
||||
action: {}
|
||||
)
|
||||
|
||||
SelectableRow(
|
||||
title: "Medium Stakes",
|
||||
subtitle: "Regular casino table",
|
||||
isSelected: false,
|
||||
badge: { BadgePill(text: "$25 - $5,000", isSelected: false) },
|
||||
action: {}
|
||||
)
|
||||
|
||||
SelectableRow(
|
||||
title: "6 Decks",
|
||||
subtitle: "Standard casino shoe",
|
||||
isSelected: false,
|
||||
action: {}
|
||||
)
|
||||
}
|
||||
|
||||
Divider().background(Color.white.opacity(0.1))
|
||||
|
||||
SettingsToggle(
|
||||
title: "Sound Effects",
|
||||
subtitle: "Play sounds for game events",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user