-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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 --------- Co-authored-by: Alaa Amin <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
efc18e1
commit c747062
Showing
42 changed files
with
621 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/* | ||
* Backpack - Skyscanner's Design System | ||
* | ||
* Copyright © 2023 Skyscanner Ltd. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
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. | ||
/// | ||
/// Use `inputState(_ state: State)` to change the state of the field set. | ||
/// | ||
/// Use `accessibilityPrefix(_ prefix: String)` to add a prefix to each accessibilityIdentifier | ||
/// that gets added to all fieldset's subcomponents | ||
|
||
// swiftlint:enable line_length | ||
|
||
public struct BPKFieldSet<Content: BPKFieldSetContentView>: View { | ||
private var state: BPKFieldSet<Content.ContentView>.State = .default | ||
private let label: String? | ||
private let content: Content | ||
private let description: String? | ||
private var accessibilityPrefix: String? | ||
|
||
public init( | ||
label: String? = nil, | ||
description: String? = nil, | ||
content: () -> Content | ||
) { | ||
self.label = label | ||
self.description = description | ||
self.content = content() | ||
} | ||
|
||
public var body: some View { | ||
VStack(alignment: .leading, spacing: .sm) { | ||
labelView | ||
content | ||
.inputState(state) | ||
.padding(.bottom, .sm) | ||
.accessibilityIdentifier(accessibilityIdentifier(for: "wrapped_view")) | ||
descriptionView | ||
if case let .error(message) = state { | ||
errorMessage(message) | ||
.padding(.top, .sm) | ||
} | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private var labelView: some View { | ||
if let label { | ||
BPKText(label, style: .label2) | ||
.lineLimit(nil) | ||
.foregroundColor(state.labelColor) | ||
.accessibilityIdentifier(accessibilityIdentifier(for: "label")) | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private var descriptionView: some View { | ||
if let description { | ||
BPKText(description, style: .caption) | ||
.lineLimit(nil) | ||
.foregroundColor(state.descriptionColor) | ||
.accessibilityIdentifier(accessibilityIdentifier(for: "descritpion")) | ||
} | ||
} | ||
|
||
private func errorMessage(_ message: String) -> some View { | ||
HStack(spacing: .md) { | ||
BPKIconView(.exclamationCircle) | ||
.foregroundColor(.textErrorColor) | ||
.accessibilityHidden(true) | ||
BPKText(message, style: .caption) | ||
.lineLimit(nil) | ||
.foregroundColor(.textErrorColor) | ||
.accessibilityIdentifier(accessibilityIdentifier(for: "error_message")) | ||
} | ||
} | ||
|
||
public func inputState(_ state: BPKFieldSet<Content.ContentView>.State) -> BPKFieldSet { | ||
var result = self | ||
result.state = state | ||
return result | ||
} | ||
} | ||
|
||
// MARK: - Accessibility | ||
|
||
extension BPKFieldSet { | ||
public func accessibilityPrefix(_ prefix: String?) -> BPKFieldSet { | ||
var result = self | ||
result.accessibilityPrefix = prefix | ||
return result | ||
} | ||
|
||
private func accessibilityIdentifier(for label: String) -> String { | ||
if let prefix = accessibilityPrefix { | ||
return "\(prefix)_\(label)" | ||
} | ||
return "" | ||
} | ||
} | ||
|
||
// MARK: - Previews | ||
|
||
// swiftlint:disable closure_body_length | ||
#Preview { | ||
return ScrollView { | ||
constructViews() | ||
} | ||
|
||
@ViewBuilder | ||
func constructViews() -> some View { | ||
VStack(spacing: .base) { | ||
BPKText("With Label & Description", style: .label1) | ||
constructFieldSet( | ||
withLabel: "Label", | ||
andDescription: "Description", | ||
wrappedView: BPKTextField(placeholder: "Enter text", .constant("")) | ||
) | ||
Divider() | ||
BPKText("With Label & No Description", style: .label1) | ||
constructFieldSet( | ||
withLabel: "Label", | ||
wrappedView: BPKTextField(placeholder: "Enter text", .constant("")) | ||
) | ||
Divider() | ||
BPKText("With No Label & Description", style: .label1) | ||
constructFieldSet( | ||
andDescription: "Description", | ||
wrappedView: BPKTextArea(.constant(""), placeholder: "Enter text") | ||
) | ||
Divider() | ||
BPKText("With No Label & No Description", style: .label1) | ||
constructFieldSet( | ||
wrappedView: BPKSelect( | ||
placeholder: "Breakfast Choices", | ||
options: ["Porridge", "Eggs", "Swift UI"], | ||
selectedIndex: .constant(1) | ||
) | ||
) | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
func constructFieldSet( | ||
withLabel label: String? = nil, | ||
andDescription description: String? = nil, | ||
wrappedView: some BPKFieldSetContentView | ||
) -> some View { | ||
ForEach([0, 1], id: \.self) { index in | ||
BPKFieldSet(label: label, description: description) { | ||
wrappedView | ||
} | ||
.if(index == 1) { view in | ||
view.inputState(.error(message: "Error Message")) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Backpack - Skyscanner's Design System | ||
* | ||
* Copyright 2018-2022 Skyscanner Ltd | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
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 | ||
} | ||
} | ||
|
||
var descriptionColor: BPKColor { | ||
switch self { | ||
case .default: return .textSecondaryColor | ||
case .error: return .textSecondaryColor | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Backpack-SwiftUI/BannerAlert | ||
|
||
[![Cocoapods](https://img.shields.io/cocoapods/v/Backpack-SwiftUI.svg?style=flat)](hhttps://cocoapods.org/pods/Backpack-SwiftUI) | ||
[![class reference](https://img.shields.io/badge/Class%20reference-iOS-blue)](https://backpack.github.io/ios/versions/latest/swiftui/Structs/BPKFieldSet.html) | ||
[![view on Github](https://img.shields.io/badge/Source%20code-GitHub-lightgrey)](https://github.com/Skyscanner/backpack-ios/tree/main/Backpack-SwiftUI/BPKFieldSet) | ||
|
||
## Default | ||
|
||
| Day | Night | | ||
| --- | --- | | ||
| <img src="https://raw.githubusercontent.com/Skyscanner/backpack-ios/main/screenshots/iPhone-swiftui_field-set___default_lm.png" alt="" width="375" /> |<img src="https://raw.githubusercontent.com/Skyscanner/backpack-ios/main/screenshots/iPhone-swiftui_field-set___default_dm.png" alt="" width="375" /> | | ||
|
||
## Error | ||
|
||
| Day | Night | | ||
| --- | --- | | ||
| <img src="https://raw.githubusercontent.com/Skyscanner/backpack-ios/main/screenshots/iPhone-swiftui_field-set___error_lm.png" alt="" width="375" /> |<img src="https://raw.githubusercontent.com/Skyscanner/backpack-ios/main/screenshots/iPhone-swiftui_field-set___error_dm.png" alt="" width="375" /> | | ||
|
||
# Usage | ||
|
||
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. | ||
|
||
|
||
## BPKFieldSet | ||
|
||
### Example of a FieldSet with Default state: | ||
|
||
```swift | ||
BPKFieldSet(label: "Label", description: "Description") { | ||
BPKTextField(placeholder: "Enter text", .constant("")) | ||
} | ||
``` | ||
|
||
### Example of a FieldSet with Error state: | ||
|
||
```swift | ||
BPKFieldSet(label: "Label", description: "Description") { | ||
BPKTextField(placeholder: "Enter text", .constant("")) | ||
} | ||
.inputState(.error(message: "Error Message")) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Backpack - Skyscanner's Design System | ||
* | ||
* Copyright © 2023 Skyscanner Ltd. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import SwiftUI | ||
|
||
extension BPKSelect: BPKFieldSetContentView { | ||
public func inputState(_ state: BPKFieldSet<BPKSelect>.State) -> BPKSelect { | ||
switch state { | ||
case .default: | ||
return inputState(BPKSelect.State.default) | ||
case .error: | ||
return inputState(BPKSelect.State.error) | ||
} | ||
} | ||
} | ||
|
||
#Preview { | ||
BPKFieldSet(label: "Label", description: "Description") { | ||
BPKSelect( | ||
placeholder: "Breakfast Choices", | ||
options: ["Porridge", "Eggs", "Swift UI"], | ||
selectedIndex: .constant(1) | ||
) | ||
} | ||
.inputState(.error(message: "Error Message")) | ||
.padding() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Backpack - Skyscanner's Design System | ||
* | ||
* Copyright 2018 Skyscanner Ltd | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import XCTest | ||
import SwiftUI | ||
@testable import Backpack_SwiftUI | ||
|
||
final class BPKFieldSetTests: XCTestCase { | ||
// swiftlint:disable large_tuple | ||
private var testsCases: [(label: String?, description: String?, name: String)] { | ||
[ | ||
("Label", "Description", "LabelAndDescription"), | ||
("Label", nil, "LabelAndNoDescription"), | ||
(nil, "Description", "NoLabelAndDescription"), | ||
(nil, nil, "NoLabelAndNoDescription") | ||
] | ||
} | ||
|
||
func test_defaultState() { | ||
testsCases.forEach { testCase in | ||
assertSnapshot( | ||
BPKFieldSet(label: testCase.label, description: testCase.description, content: { | ||
BPKTextField(placeholder: "Enter text", .constant("")) | ||
}) | ||
.frame(width: 300), | ||
testName: "test_defaultStateWith\(testCase.name)" | ||
) | ||
} | ||
} | ||
|
||
func test_errorState() { | ||
testsCases.forEach { testCase in | ||
assertSnapshot( | ||
BPKFieldSet(label: testCase.label, description: testCase.description, content: { | ||
BPKTextField(placeholder: "Enter text", .constant("")) | ||
}) | ||
.inputState(.error(message: "Error Message")) | ||
.frame(width: 300), | ||
testName: "test_errorStateWith\(testCase.name)" | ||
) | ||
} | ||
} | ||
} |
Binary file added
BIN
+9.09 KB
...shots__/BPKFieldSetTests/test_defaultStateWithLabelAndDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+8.39 KB
...hots__/BPKFieldSetTests/test_defaultStateWithLabelAndDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+8.48 KB
...__Snapshots__/BPKFieldSetTests/test_defaultStateWithLabelAndDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.99 KB
...ots__/BPKFieldSetTests/test_defaultStateWithLabelAndNoDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.54 KB
...ts__/BPKFieldSetTests/test_defaultStateWithLabelAndNoDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.61 KB
...Snapshots__/BPKFieldSetTests/test_defaultStateWithLabelAndNoDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+6.83 KB
...ots__/BPKFieldSetTests/test_defaultStateWithNoLabelAndDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+6.3 KB
...ts__/BPKFieldSetTests/test_defaultStateWithNoLabelAndDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+6.41 KB
...Snapshots__/BPKFieldSetTests/test_defaultStateWithNoLabelAndDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+4.08 KB
...s__/BPKFieldSetTests/test_defaultStateWithNoLabelAndNoDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+3.73 KB
...__/BPKFieldSetTests/test_defaultStateWithNoLabelAndNoDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+3.8 KB
...apshots__/BPKFieldSetTests/test_defaultStateWithNoLabelAndNoDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14 KB
...apshots__/BPKFieldSetTests/test_errorStateWithLabelAndDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+13.2 KB
...pshots__/BPKFieldSetTests/test_errorStateWithLabelAndDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+13.3 KB
...t/__Snapshots__/BPKFieldSetTests/test_errorStateWithLabelAndDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+11.3 KB
...shots__/BPKFieldSetTests/test_errorStateWithLabelAndNoDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+10.7 KB
...hots__/BPKFieldSetTests/test_errorStateWithLabelAndNoDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+10.7 KB
...__Snapshots__/BPKFieldSetTests/test_errorStateWithLabelAndNoDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+11.8 KB
...shots__/BPKFieldSetTests/test_errorStateWithNoLabelAndDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+11.2 KB
...hots__/BPKFieldSetTests/test_errorStateWithNoLabelAndDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+11.4 KB
...__Snapshots__/BPKFieldSetTests/test_errorStateWithNoLabelAndDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+9.06 KB
...ots__/BPKFieldSetTests/test_errorStateWithNoLabelAndNoDescription.dark-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+8.63 KB
...ts__/BPKFieldSetTests/test_errorStateWithNoLabelAndNoDescription.light-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+8.7 KB
...Snapshots__/BPKFieldSetTests/test_errorStateWithNoLabelAndNoDescription.rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.