Files
todos/App/Sources/Views/TaskList/TaskListItem.swift
T

149 lines
4.2 KiB
Swift
Raw Normal View History

2026-02-10 15:31:52 +00:00
import SwiftUI
extension TaskListView {
/// A view that displays a single editable todo task in a list.
///
/// This view provides a comprehensive interface for viewing and editing task information,
/// including:
/// - A completion toggle using a circular checkbox design
/// - An editable title field with multi-line support
/// - An optional note field that appears when the task has notes
/// - A date picker for selecting the task's due date
///
/// The view automatically adjusts its appearance based on completion state, dimming
/// completed tasks to provide visual feedback. All text fields support automatic
/// capitalization and keyboard correction for improved user experience.
struct Item: View {
// MARK: Properties
/// The todo task to display.
@Bindable
private var item: Todo
// MARK: Initializers
/// Creates a new task list item view for the specified todo.
///
/// The item is wrapped in a `@Bindable` property wrapper to enable two-way
/// data binding for editing task properties directly within the view.
///
/// - Parameter item: The todo task to display and edit.
init(_ item: Todo) {
_item = Bindable(item)
}
// MARK: Body
/// The content and behavior of the view.
///
/// Renders an editable task list item with a completion toggle, title field,
/// optional note field, and date picker. The view's foreground color automatically
/// adjusts based on completion state, and changes animate smoothly.
var body: some View {
HStack(
alignment: .top,
spacing: 16
) {
Toggle(
"view.task-list-item.placeholder.completed",
isOn: isCompleted
)
.toggleStyle(.completion)
VStack(
alignment: .leading,
spacing: 8
) {
TextField(
"view.task-list-item.placeholder.title",
text: $item.title,
axis: .vertical
)
.font(.headline)
if showNote {
TextField(
"view.task-list-item.placeholder.note",
text: note,
axis: .vertical
)
.font(.subheadline)
}
DatePicker(selection: $item.due, displayedComponents: .date) {
Image(systemName: "calendar")
}
.frame(width: 160)
.disabled(isCompleted.wrappedValue)
}
.autocapitalization(.sentences)
.autocorrectionDisabled(false)
.foregroundStyle(foregroundColor)
.keyboardType(.asciiCapable)
}
.animation(
.easeInOut,
value: item.completed
)
}
}
}
// MARK: - Helpers
private extension TaskListView.Item {
// MARK: Bindings
var isCompleted: Binding<Bool> {
.init {
item.completed != nil
} set: {
item.completed = $0 ? .now : nil
}
}
var note: Binding<String> {
.init {
item.note ?? ""
} set: {
item.note = $0.isEmpty ? nil : $0
}
}
// MARK: Computed
var foregroundColor: Color {
item.completed == nil ? .primary : .secondary
}
var showNote: Bool {
guard let note = item.note else {
return false
}
return note.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
}
}
// MARK: - Previews
#Preview("Task list item") {
@Previewable
var todo: Todo = .init(
title: "Complete project documentation",
note: "Add detailed comments and README",
due: .now,
completed: nil
)
List {
TaskListView.Item(todo)
}
.listStyle(.grouped)
}