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.

200 lines
6.0 KiB

import SwiftUI
// MARK: -
struct TextEditorView: View {
let title: String
let isRequired: Bool
let placeholder: String
let text: Binding<String>
let maxCharacters: Int
let minHeight: CGFloat
let icon: String?
let isFocused: Bool
let onFocusChange: (Bool) -> Void
init(
title: String,
isRequired: Bool = false,
placeholder: String,
text: Binding<String>,
maxCharacters: Int = 1000,
minHeight: CGFloat = 120,
icon: String? = nil,
isFocused: Bool = false,
onFocusChange: @escaping (Bool) -> Void = { _ in }
) {
self.title = title
self.isRequired = isRequired
self.placeholder = placeholder
self.text = text
self.maxCharacters = maxCharacters
self.minHeight = minHeight
self.icon = icon
self.isFocused = isFocused
self.onFocusChange = onFocusChange
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
InputTitleView.required(title, icon: icon)
ZStack {
//
TextEditor(text: text)
.frame(minHeight: minHeight)
.padding(8)
.background(Color(.systemBackground))
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(isFocused ? Color.blue : Color(.systemGray4), lineWidth: 1)
)
.onChange(of: text.wrappedValue) { newValue in
//
if newValue.count > maxCharacters {
text.wrappedValue = String(newValue.prefix(maxCharacters))
}
}
.onTapGesture {
onFocusChange(true)
}
// -
if text.wrappedValue.isEmpty && !isFocused {
VStack {
HStack {
Text(placeholder)
.foregroundColor(.secondary)
.font(.body)
Spacer()
}
Spacer()
}
.padding(16)
.allowsHitTesting(false)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
//
HStack {
Spacer()
Text("\(text.wrappedValue.count)/\(maxCharacters)")
.font(.caption)
.foregroundColor(getCharacterCountColor())
}
}
}
private func getCharacterCountColor() -> Color {
let count = text.wrappedValue.count
if count >= maxCharacters {
return .orange
} else if count >= Int(Double(maxCharacters) * 0.9) {
return .blue
} else {
return .secondary
}
}
}
// MARK: -
extension TextEditorView {
static func description(
title: String,
isRequired: Bool = false,
placeholder: String,
text: Binding<String>,
maxCharacters: Int = 500,
icon: String? = nil,
isFocused: Bool = false,
onFocusChange: @escaping (Bool) -> Void = { _ in }
) -> TextEditorView {
TextEditorView(
title: title,
isRequired: isRequired,
placeholder: placeholder,
text: text,
maxCharacters: maxCharacters,
minHeight: 100,
icon: icon,
isFocused: isFocused,
onFocusChange: onFocusChange
)
}
static func longText(
title: String,
isRequired: Bool = false,
placeholder: String,
text: Binding<String>,
maxCharacters: Int = 1000,
icon: String? = nil,
isFocused: Bool = false,
onFocusChange: @escaping (Bool) -> Void = { _ in }
) -> TextEditorView {
TextEditorView(
title: title,
isRequired: isRequired,
placeholder: placeholder,
text: text,
maxCharacters: maxCharacters,
minHeight: 150,
icon: icon,
isFocused: isFocused,
onFocusChange: onFocusChange
)
}
static func emailBody(
title: String,
isRequired: Bool = false,
placeholder: String,
text: Binding<String>,
maxCharacters: Int = 1200,
icon: String? = nil,
isFocused: Bool = false,
onFocusChange: @escaping (Bool) -> Void = { _ in }
) -> TextEditorView {
TextEditorView(
title: title,
isRequired: isRequired,
placeholder: placeholder,
text: text,
maxCharacters: maxCharacters,
minHeight: 120,
icon: icon,
isFocused: isFocused,
onFocusChange: onFocusChange
)
}
}
#Preview {
VStack(spacing: 16) {
TextEditorView.description(
title: "description".localized,
isRequired: true,
placeholder: "enter_description_content".localized,
text: .constant(""),
icon: "text.quote"
)
TextEditorView.longText(
title: "long_text".localized,
isRequired: false,
placeholder: "enter_long_text_content".localized,
text: .constant(""),
icon: "doc.text"
)
TextEditorView.emailBody(
title: "email_body".localized,
isRequired: true,
placeholder: "enter_email_body_content".localized,
text: .constant(""),
icon: "envelope"
)
}
.padding()
}