Skip to content

Commit

Permalink
AI-638: FieldSet Component (#2054)
Browse files Browse the repository at this point in the history
* AI-638: FieldSet componenet

* AI-638: Screenshots

* AI-638: Accessibility prefix & comments

* record snapshots

* AI-638: Readme

* Updated snapshots

* Swiftlint

* AI-638: change description to constant

* AI-638: Address PR comments

* AI-638: Dispatch state changes using the environment modifier

* Delete files

* AI-638: Handle environment value in wrapped views

* Update accessbilityId

* Rename _state to state, and state to resolvedState

---------

Co-authored-by: Alaa Amin <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 29, 2024
1 parent 7d81066 commit f77ef9b
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 172 deletions.
50 changes: 31 additions & 19 deletions Backpack-SwiftUI/FieldSet/Classes/BPKFieldSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,10 @@
import SwiftUI
import UIKit

public protocol BPKFieldSetContentView: View {
associatedtype ContentView: BPKFieldSetContentView

func inputState(_ state: BPKFieldSet<ContentView>.State) -> ContentView
}

// swiftlint:disable line_length

/// A component which wraps its content (view) and optionally adds a title, description and error label (depending on the field's state) around it.
/// Supported states are Default, and Error. The states are dispatched to the wrapped view. The wrapped view must conform to `BPKFieldSetStatusHandling` to ensure it can handle the dispatched state.
/// Supported states are Default, and Error. The states are dispatched to the wrapped view through the .environment modifier.
///
/// Use `inputState(_ state: State)` to change the state of the field set.
///
Expand All @@ -37,8 +31,8 @@ public protocol BPKFieldSetContentView: View {

// swiftlint:enable line_length

public struct BPKFieldSet<Content: BPKFieldSetContentView>: View {
private var state: BPKFieldSet<Content.ContentView>.State = .default
public struct BPKFieldSet<Content: View>: View {
private var state: BPKFieldSetState = .default
private let label: String?
private let content: Content
private let description: String?
Expand All @@ -47,7 +41,7 @@ public struct BPKFieldSet<Content: BPKFieldSetContentView>: View {
public init(
label: String? = nil,
description: String? = nil,
content: () -> Content
@ViewBuilder content: () -> Content
) {
self.label = label
self.description = description
Expand All @@ -58,9 +52,9 @@ public struct BPKFieldSet<Content: BPKFieldSetContentView>: View {
VStack(alignment: .leading, spacing: .sm) {
labelView
content
.inputState(state)
.padding(.bottom, .sm)
.accessibilityIdentifier(accessibilityIdentifier(for: "wrapped_view"))
.environment(\.bpkFieldSetState, state)
.accessibilityIdentifier(accessibilityIdentifier(for: AccessibilityID.content))
descriptionView
if case let .error(message) = state {
errorMessage(message)
Expand All @@ -75,7 +69,7 @@ public struct BPKFieldSet<Content: BPKFieldSetContentView>: View {
BPKText(label, style: .label2)
.lineLimit(nil)
.foregroundColor(state.labelColor)
.accessibilityIdentifier(accessibilityIdentifier(for: "label"))
.accessibilityIdentifier(accessibilityIdentifier(for: AccessibilityID.label))
}
}

Expand All @@ -85,7 +79,7 @@ public struct BPKFieldSet<Content: BPKFieldSetContentView>: View {
BPKText(description, style: .caption)
.lineLimit(nil)
.foregroundColor(state.descriptionColor)
.accessibilityIdentifier(accessibilityIdentifier(for: "descritpion"))
.accessibilityIdentifier(accessibilityIdentifier(for: AccessibilityID.description))
}
}

Expand All @@ -97,11 +91,11 @@ public struct BPKFieldSet<Content: BPKFieldSetContentView>: View {
BPKText(message, style: .caption)
.lineLimit(nil)
.foregroundColor(.textErrorColor)
.accessibilityIdentifier(accessibilityIdentifier(for: "error_message"))
.accessibilityIdentifier(accessibilityIdentifier(for: AccessibilityID.errorMessage))
}
}

public func inputState(_ state: BPKFieldSet<Content.ContentView>.State) -> BPKFieldSet {
public func inputState(_ state: BPKFieldSetState) -> BPKFieldSet {
var result = self
result.state = state
return result
Expand All @@ -117,12 +111,30 @@ extension BPKFieldSet {
return result
}

private func accessibilityIdentifier(for label: String) -> String {
private func accessibilityIdentifier(for subview: AccessibilityID) -> String {
if let prefix = accessibilityPrefix {
return "\(prefix)_\(label)"
return "\(prefix)_\(subview.rawValue)"
}
return ""
}

private enum AccessibilityID: String {
case label
case content = "content_view"
case description
case errorMessage = "error_message"
}
}

struct BPKFieldSetStateKey: EnvironmentKey {
static var defaultValue: BPKFieldSetState?
}

extension EnvironmentValues {
var bpkFieldSetState: BPKFieldSetState? {
get { self[BPKFieldSetStateKey.self] } // swiftlint:disable:this implicit_getter
set { self[BPKFieldSetStateKey.self] = newValue }
}
}

// MARK: - Previews
Expand Down Expand Up @@ -170,7 +182,7 @@ extension BPKFieldSet {
func constructFieldSet(
withLabel label: String? = nil,
andDescription description: String? = nil,
wrappedView: some BPKFieldSetContentView
wrappedView: some View
) -> some View {
ForEach([0, 1], id: \.self) { index in
BPKFieldSet(label: label, description: description) {
Expand Down
38 changes: 18 additions & 20 deletions Backpack-SwiftUI/FieldSet/Classes/BPKFieldSetState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,25 @@

import SwiftUI

extension BPKFieldSet {
/// The state of the field set.
public enum State {
/// Default state.
case `default`
/// Error state
/// Adds an errors message beneath the description or the wrapped view if no description is provided.
case error(message: String)

var labelColor: BPKColor {
switch self {
case .default: return .textPrimaryColor
case .error: return .textErrorColor
}
/// The state of the field set.
public enum BPKFieldSetState: Equatable {
/// Default state.
case `default`
/// Error state
/// Adds an errors message beneath the description or the wrapped view if no description is provided.
case error(message: String)

var labelColor: BPKColor {
switch self {
case .default: return .textPrimaryColor
case .error: return .textErrorColor
}
var descriptionColor: BPKColor {
switch self {
case .default: return .textSecondaryColor
case .error: return .textSecondaryColor
}
}

var descriptionColor: BPKColor {
switch self {
case .default: return .textSecondaryColor
case .error: return .textSecondaryColor
}
}
}
2 changes: 1 addition & 1 deletion Backpack-SwiftUI/FieldSet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

FieldSet is a component which wraps its content (view) and optionally adds a title, description and error label (depending on the field's state) around it.

Supported states are Default, and Error. The states are dispatched to the wrapped view. The wrapped view must conform to `BPKFieldSetContentView` to ensure it can handle the dispatched state.
Supported states are Default, and Error. The states are dispatched to the wrapped view through the .environment modifier.


## BPKFieldSet
Expand Down
42 changes: 0 additions & 42 deletions Backpack-SwiftUI/Select/Classes/BPKSelect+FieldSet.swift

This file was deleted.

13 changes: 12 additions & 1 deletion Backpack-SwiftUI/Select/Classes/BPKSelect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,22 @@ extension View {
}

public struct BPKSelect: View {
@Environment(\.bpkFieldSetState) var fieldSetState
@Binding private var selectedIndex: Int?

private let options: [String]
private let placeholder: String
private var state: State = .default
private var resolvedState: State {
switch fieldSetState {
case .default:
return .default
case .error:
return .error
default:
return state
}
}

var labelText: String {
// If nil or invalid value is passed in by consumer we display the placeholder
Expand Down Expand Up @@ -85,7 +96,7 @@ public struct BPKSelect: View {
.bpkPickerStyle(
CustomPickerStyle(
labelText: labelText,
pickerState: state
pickerState: resolvedState
)
)
}
Expand Down
39 changes: 0 additions & 39 deletions Backpack-SwiftUI/TextArea/Classes/BPKTextArea+FieldSet.swift

This file was deleted.

16 changes: 14 additions & 2 deletions Backpack-SwiftUI/TextArea/Classes/BPKTextArea.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,21 @@ public struct BPKTextArea: View {
}

@Environment(\.colorScheme) var colorScheme
@Environment(\.bpkFieldSetState) var fieldSetState
@Binding private var value: String
private let placeholder: String?
private var state: State = .default
private var resolvedState: State {
switch fieldSetState {
case .default:
return .default
case .error:
return .error
default:
return state
}
}

private var accessibilityLabelText: String {
if let placeholder = placeholder, value.isEmpty {
return placeholder
Expand Down Expand Up @@ -113,14 +125,14 @@ public struct BPKTextArea: View {
.clipShape(
RoundedRectangle(cornerRadius: BorderConstants.cornerRadius)
)
.outline(state.borderColor, cornerRadius: BorderConstants.cornerRadius)
.outline(resolvedState.borderColor, cornerRadius: BorderConstants.cornerRadius)
.frame(minHeight: frameHeight)
.accessibilityLabel(accessibilityLabelText)
}

@ViewBuilder
private var accessory: some View {
if let icon = state.icon {
if let icon = resolvedState.icon {
BPKIconView(icon.icon)
.foregroundColor(icon.color)
.accessibilityHidden(true)
Expand Down
38 changes: 0 additions & 38 deletions Backpack-SwiftUI/TextField/Classes/BPKTextField+FieldSet.swift

This file was deleted.

Loading

0 comments on commit f77ef9b

Please sign in to comment.