Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OGM-2374: New component - Navigation Tab Group (#1929)
* OGM-2374: Create components * OGM-2374: Move components, show in example app * OGM-2374: Disable force-update selectedIndex * OGM-2374: Add navigation tab tests * OGM-2374: Add navigation tab group tests * OGM-2374: Code review feedback * OGM-2374: Make BPKText able to inherit foreground * Revert "OGM-2374: Make BPKText able to inherit foreground" This reverts commit 2019e8a. * OGM-2374: Set font for text * Record snapshots * Updated snapshots --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
09680be
commit 706c981
Showing
30 changed files
with
503 additions
and
0 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
Backpack-SwiftUI/NavigationTab/Classes/BPKNavigationTab+Style.swift
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,25 @@ | ||
/* | ||
* 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 SwiftUI | ||
|
||
public extension BPKNavigationTabGroup { | ||
enum Style { | ||
case `default`, onDark | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
Backpack-SwiftUI/NavigationTab/Classes/BPKNavigationTab.swift
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,84 @@ | ||
/* | ||
* 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 SwiftUI | ||
|
||
struct BPKNavigationTab: View { | ||
|
||
let text: String | ||
let icon: BPKIcon? | ||
let selected: Bool | ||
let style: BPKNavigationTabGroup.Style | ||
let onClick: () -> Void | ||
|
||
init( | ||
_ text: String, | ||
icon: BPKIcon? = nil, | ||
selected: Bool = false, | ||
style: BPKNavigationTabGroup.Style = .default, | ||
onClick: @escaping () -> Void | ||
) { | ||
self.text = text | ||
self.icon = icon | ||
self.selected = selected | ||
self.style = style | ||
self.onClick = onClick | ||
} | ||
|
||
var body: some View { | ||
|
||
Button(action: onClick) { | ||
HStack(spacing: .md) { | ||
if let icon { | ||
BPKIconView(icon) | ||
} | ||
Text(text) | ||
.font(BPKFontStyle.label2.font) | ||
.lineLimit(1) | ||
} | ||
.padding(.horizontal, .base) | ||
} | ||
.buttonStyle( | ||
NavigationTabStyle( | ||
style: style, | ||
selected: selected | ||
) | ||
) | ||
.accessibilityAddTraits(selected ? [.isSelected] : []) | ||
.if(!BPKFont.enableDynamicType, transform: { | ||
$0.sizeCategory(.large) | ||
}) | ||
} | ||
} | ||
|
||
struct BPKNavigationTab_Previews: PreviewProvider { | ||
static var previews: some View { | ||
VStack { | ||
HStack { | ||
BPKNavigationTab("Explore", icon: .explore, selected: true) {} | ||
BPKNavigationTab("Flights", icon: .flight) {} | ||
} | ||
HStack { | ||
BPKNavigationTab("Explore", icon: .explore, selected: true, style: .onDark) {} | ||
BPKNavigationTab("Flights", icon: .flight, style: .onDark) {} | ||
} | ||
.padding() | ||
.background(.surfaceContrastColor) | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
Backpack-SwiftUI/NavigationTab/Classes/BPKNavigationTabGroup.swift
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,112 @@ | ||
/* | ||
* 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 SwiftUI | ||
|
||
/// A Group of tabs that allows a single tab to be selected at a time. | ||
public struct BPKNavigationTabGroup: View { | ||
private let tabs: [Item] | ||
private let style: BPKNavigationTabGroup.Style | ||
private let onItemClick: (_ index: Int) -> Void | ||
|
||
@Binding private var selectedIndex: Int | ||
|
||
public init( | ||
tabs: [Item], | ||
style: BPKNavigationTabGroup.Style = .default, | ||
selectedIndex: Binding<Int>, | ||
onItemClick: @escaping (_ index: Int) -> Void | ||
) { | ||
self.tabs = tabs | ||
self.style = style | ||
self._selectedIndex = selectedIndex | ||
self.onItemClick = onItemClick | ||
} | ||
|
||
public var body: some View { | ||
ScrollView(.horizontal, showsIndicators: false) { | ||
HStack(spacing: .md) { | ||
ForEach(Array(tabs.enumerated()), id: \.element) { index, item in | ||
tab(for: item, index: index) | ||
} | ||
} | ||
.padding(1) // to account for chip outlines | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func tab(for tab: Item, index: Int) -> some View { | ||
BPKNavigationTab( | ||
tab.text, | ||
icon: tab.icon, | ||
selected: selectedIndex == index, | ||
style: style | ||
) { | ||
onItemClick(index) | ||
} | ||
} | ||
} | ||
|
||
public extension BPKNavigationTabGroup { | ||
struct Item: Hashable { | ||
let text: String | ||
let icon: BPKIcon? | ||
|
||
public init(text: String, icon: BPKIcon? = nil) { | ||
self.text = text | ||
self.icon = icon | ||
} | ||
|
||
public static func == (lhs: Item, rhs: Item) -> Bool { | ||
lhs.text == rhs.text && lhs.icon?.name == rhs.icon?.name | ||
} | ||
|
||
public func hash(into hasher: inout Hasher) { | ||
hasher.combine(text) | ||
hasher.combine(icon?.name) | ||
} | ||
} | ||
} | ||
|
||
struct BPKNavigationTabGroup_Previews: PreviewProvider { | ||
|
||
static let tabs: [BPKNavigationTabGroup.Item] = [ | ||
.init(text: "Explore", icon: .explore), | ||
.init(text: "Flights", icon: .flight), | ||
.init(text: "Hotels", icon: .hotels), | ||
.init(text: "Car Hire", icon: .cars) | ||
] | ||
|
||
static var previews: some View { | ||
VStack { | ||
BPKNavigationTabGroup( | ||
tabs: tabs, | ||
selectedIndex: .constant(0) | ||
) { _ in } | ||
.padding() | ||
|
||
BPKNavigationTabGroup( | ||
tabs: tabs, | ||
style: .onDark, | ||
selectedIndex: .constant(0) | ||
) { _ in } | ||
.padding() | ||
.background(.surfaceContrastColor) | ||
} | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
Backpack-SwiftUI/NavigationTab/Classes/NavigationTabStyle.swift
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,70 @@ | ||
/* | ||
* 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 SwiftUI | ||
|
||
struct NavigationTabStyle: ButtonStyle { | ||
let style: BPKNavigationTabGroup.Style | ||
let selected: Bool | ||
|
||
func makeBody(configuration: Self.Configuration) -> some View { | ||
configuration.label | ||
.frame(minHeight: .xl) | ||
.fixedSize(horizontal: true, vertical: false) | ||
.background(backgroundColor(configuration.isPressed)) | ||
.foregroundColor(foregroundColor(configuration.isPressed)) | ||
.outline(outlineColor(configuration.isPressed), cornerRadius: .lg) | ||
.clipShape(RoundedRectangle(cornerRadius: .lg)) | ||
.if(!BPKFont.enableDynamicType, transform: { | ||
$0.sizeCategory(.large) | ||
}) | ||
} | ||
|
||
private func outlineColor(_ isPressed: Bool) -> BPKColor { | ||
switch style { | ||
case .`default`: | ||
if selected || isPressed { | ||
return .coreAccentColor | ||
} | ||
|
||
return .lineColor | ||
case .onDark: | ||
if selected { | ||
return .coreAccentColor | ||
} else if isPressed { | ||
return .chipOnDarkPressedStrokeColor | ||
} else { | ||
return .lineOnDarkColor | ||
} | ||
} | ||
} | ||
|
||
private func backgroundColor(_ isPressed: Bool) -> BPKColor { | ||
return selected ? .coreAccentColor : .clear | ||
} | ||
|
||
private func foregroundColor(_ isPressed: Bool) -> BPKColor { | ||
if selected { | ||
return .textPrimaryInverseColor | ||
} else if isPressed { | ||
return style == .default ? .buttonLinkPressedForegroundColor : .buttonLinkOnDarkPressedForegroundColor | ||
} else { | ||
return style == .default ? .textPrimaryColor : .textOnDarkColor | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
Backpack-SwiftUI/Tests/NavigationTab/BPKNavigationTabGroupTests.swift
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,61 @@ | ||
/* | ||
* 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 | ||
|
||
class BPKNavigationTabGroupTests: XCTestCase { | ||
|
||
private let tabs: [BPKNavigationTabGroup.Item] = [ | ||
.init(text: "Explore", icon: .explore), | ||
.init(text: "Flights", icon: .flight), | ||
.init(text: "Hotels", icon: .hotels), | ||
.init(text: "Car Hire", icon: .cars) | ||
] | ||
|
||
func test_default() { | ||
assertSnapshot( | ||
VStack { | ||
BPKNavigationTabGroup( | ||
tabs: tabs, | ||
selectedIndex: .constant(0), | ||
onItemClick: { _ in } | ||
) | ||
.padding() | ||
} | ||
.frame(width: 300) | ||
) | ||
} | ||
|
||
func test_dark() { | ||
assertSnapshot( | ||
VStack { | ||
BPKNavigationTabGroup( | ||
tabs: tabs, | ||
style: .onDark, | ||
selectedIndex: .constant(0), | ||
onItemClick: { _ in } | ||
) | ||
.padding() | ||
.background(.surfaceContrastColor) | ||
} | ||
.frame(width: 300) | ||
) | ||
} | ||
} |
Oops, something went wrong.