You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
7.3 KiB

import SwiftUI
// MARK: -
struct FormView<Content: View>: View {
let title: String?
let content: Content
let spacing: CGFloat
let padding: EdgeInsets
init(
title: String? = nil,
spacing: CGFloat = 16,
padding: EdgeInsets = EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20),
@ViewBuilder content: () -> Content
) {
self.title = title
self.spacing = spacing
self.padding = padding
self.content = content()
}
var body: some View {
ScrollView {
VStack(spacing: spacing) {
if let title = title {
HStack {
Text(title)
.font(.title2)
.fontWeight(.semibold)
.foregroundColor(.primary)
Spacer()
}
}
content
}
.padding(padding)
}
.background(Color(.systemGroupedBackground))
}
}
// MARK: -
struct FormGroup<Content: View>: View {
let title: String?
let content: Content
let spacing: CGFloat
init(
title: String? = nil,
spacing: CGFloat = 12,
@ViewBuilder content: () -> Content
) {
self.title = title
self.spacing = spacing
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: spacing) {
if let title = title {
HStack {
Text(title)
.font(.headline)
.foregroundColor(.primary)
Spacer()
}
}
content
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
}
}
// MARK: -
struct FormRow<Content: View>: View {
let title: String
let isRequired: Bool
let content: Content
let icon: String?
init(
title: String,
isRequired: Bool = false,
icon: String? = nil,
@ViewBuilder content: () -> Content
) {
self.title = title
self.isRequired = isRequired
self.icon = icon
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
if let icon = icon {
Image(systemName: icon)
.font(.subheadline)
.foregroundColor(.blue)
}
Text(title)
.font(.subheadline)
.foregroundColor(.primary)
if isRequired {
Text("*")
.foregroundColor(.red)
.font(.subheadline)
}
Spacer()
}
content
}
}
}
// MARK: -
struct FormActionButton: View {
let title: String
let action: () -> Void
let isEnabled: Bool
let style: ButtonStyle
let icon: String?
enum ButtonStyle {
case primary
case secondary
case destructive
case success
}
init(
title: String,
action: @escaping () -> Void,
isEnabled: Bool = true,
style: ButtonStyle = .primary,
icon: String? = nil
) {
self.title = title
self.action = action
self.isEnabled = isEnabled
self.style = style
self.icon = icon
}
var body: some View {
Button(action: action) {
HStack(spacing: 8) {
if let icon = icon {
Image(systemName: icon)
.font(.system(size: 16, weight: .medium))
}
Text(title)
.font(.system(size: 16, weight: .semibold))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(backgroundColor)
)
.foregroundColor(foregroundColor)
}
.disabled(!isEnabled)
.opacity(isEnabled ? 1.0 : 0.6)
}
private var backgroundColor: Color {
switch style {
case .primary:
return .blue
case .secondary:
return Color(.systemGray5)
case .destructive:
return .red
case .success:
return .green
}
}
private var foregroundColor: Color {
switch style {
case .primary, .destructive, .success:
return .white
case .secondary:
return .primary
}
}
}
// MARK: -
extension FormActionButton {
static func primary(
title: String,
action: @escaping () -> Void,
isEnabled: Bool = true,
icon: String? = nil
) -> FormActionButton {
FormActionButton(
title: title,
action: action,
isEnabled: isEnabled,
style: .primary,
icon: icon
)
}
static func secondary(
title: String,
action: @escaping () -> Void,
isEnabled: Bool = true,
icon: String? = nil
) -> FormActionButton {
FormActionButton(
title: title,
action: action,
isEnabled: isEnabled,
style: .secondary,
icon: icon
)
}
static func destructive(
title: String,
action: @escaping () -> Void,
isEnabled: Bool = true,
icon: String? = nil
) -> FormActionButton {
FormActionButton(
title: title,
action: action,
isEnabled: isEnabled,
style: .destructive,
icon: icon
)
}
static func success(
title: String,
action: @escaping () -> Void,
isEnabled: Bool = true,
icon: String? = nil
) -> FormActionButton {
FormActionButton(
title: title,
action: action,
isEnabled: isEnabled,
style: .success,
icon: icon
)
}
}
#Preview {
FormView(title: "sample_form".localized) {
FormGroup(title: "basic_info".localized) {
FormRow(title: "username".localized, isRequired: true, icon: "person") {
TextField("enter_username".localized, text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
}
FormRow(title: "email".localized, isRequired: true, icon: "envelope") {
TextField("enter_email".localized, text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.emailAddress)
}
}
FormGroup(title: "actions".localized) {
FormActionButton.primary(title: "save".localized, action: {})
FormActionButton.secondary(title: "cancel".localized, action: {})
}
}
}