Bedrock/Sources/Bedrock/Views/Settings/SettingsRow.swift

134 lines
3.8 KiB
Swift

//
// SettingsRow.swift
// Bedrock
//
// A simple settings row with icon, title, and optional value/accessory.
//
import SwiftUI
/// A simple settings row with icon, title, and optional value/accessory.
public struct SettingsRow<Accessory: View>: View {
/// The row icon (SF Symbol name).
public let systemImage: String
/// The row title.
public let title: String
/// Optional value text.
public let value: String?
/// The icon background color.
public let iconColor: Color
/// Optional accessory view.
public let accessory: Accessory?
/// Action when tapped.
public let action: () -> Void
/// Creates a settings row.
/// - Parameters:
/// - systemImage: SF Symbol name for the icon.
/// - title: The row title.
/// - value: Optional value text.
/// - iconColor: The icon background color (default: primary accent).
/// - accessory: Optional accessory view.
/// - action: Action when tapped.
public init(
systemImage: String,
title: String,
value: String? = nil,
iconColor: Color = .Accent.primary,
@ViewBuilder accessory: () -> Accessory? = { nil as EmptyView? },
action: @escaping () -> Void
) {
self.systemImage = systemImage
self.title = title
self.value = value
self.iconColor = iconColor
self.accessory = accessory()
self.action = action
}
public var body: some View {
Button(action: action) {
HStack(spacing: Design.Spacing.medium) {
SymbolIcon(systemImage, size: .inline, color: .white)
.frame(width: Design.Size.iconContainerSmall, height: Design.Size.iconContainerSmall)
.background(iconColor.opacity(Design.Opacity.heavy))
.clipShape(.rect(cornerRadius: Design.CornerRadius.xSmall))
Text(title).styled(.subheading)
Spacer()
if let value {
Text(value).styled(.subheading, emphasis: .secondary)
}
if let accessory {
accessory
} else {
SymbolIcon.chevron()
}
}
.padding(.vertical, Design.Spacing.medium)
.padding(.horizontal, Design.Spacing.medium)
.background(Color(.secondarySystemGroupedBackground))
.clipShape(.rect(cornerRadius: Design.CornerRadius.small))
}
.buttonStyle(.plain)
}
}
// MARK: - Convenience Initializer
extension SettingsRow where Accessory == EmptyView {
/// Creates a settings row without an accessory.
public init(
systemImage: String,
title: String,
value: String? = nil,
iconColor: Color = .Accent.primary,
action: @escaping () -> Void
) {
self.systemImage = systemImage
self.title = title
self.value = value
self.iconColor = iconColor
self.accessory = nil
self.action = action
}
}
// MARK: - Preview
#Preview {
VStack(spacing: Design.Spacing.small) {
SettingsRow(
systemImage: "star.fill",
title: "Rate App",
iconColor: .Status.warning,
action: {}
)
SettingsRow(
systemImage: "envelope.fill",
title: "Contact Us",
value: "support@example.com",
iconColor: .Status.info,
action: {}
)
SettingsRow(
systemImage: "bell.fill",
title: "Notifications",
iconColor: .Status.error,
action: {}
)
}
.padding()
.background(Color.Surface.overlay)
}