Skip to content

Commit 52dee91

Browse files
authored
[Woo POS][Design System] Extract page header to a component and use it in root/child item lists (#15158)
2 parents 5282088 + 4269cf7 commit 52dee91

File tree

6 files changed

+202
-33
lines changed

6 files changed

+202
-33
lines changed

WooCommerce/Classes/POS/Presentation/CartView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ private extension CartView {
214214
static let cartHeaderElementSpacing: CGFloat = 16
215215
static let cartAnimation: Animation = .spring(duration: 0.2)
216216
static let checkoutButtonVerticalPadding: CGFloat = 16
217-
static let cartItemSpacing: CGFloat = 16
217+
static let cartItemSpacing: CGFloat = 8
218218
}
219219

220220
enum Localization {

WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,11 @@ struct ChildItemList: View {
4545
@available(iOS 17.0, *)
4646
private extension ChildItemList {
4747
@ViewBuilder var headerView: some View {
48-
HStack {
49-
Button {
50-
dismiss()
51-
} label: {
52-
Image(systemName: "chevron.backward")
53-
.font(.posBodyLargeBold)
54-
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
55-
.foregroundColor(.posOnSurface)
56-
}
57-
POSHeaderTitleView(title: title)
58-
Spacer()
59-
}
60-
.padding(.horizontal, Constants.itemListPadding)
48+
POSPageHeaderView(title: title,
49+
backButtonConfiguration: .init(state: .enabled,
50+
action: {
51+
dismiss()
52+
}))
6153
}
6254

6355
@ViewBuilder

WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ struct ItemList<HeaderView: View>: View {
7373

7474
private enum Constants {
7575
static let itemListPadding: CGFloat = 16
76-
static let itemSpacing: CGFloat = 16
76+
static let itemSpacing: CGFloat = 8
7777
}
7878

7979
@available(iOS 17.0, *)

WooCommerce/Classes/POS/Presentation/ItemListView.swift

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,18 @@ private extension ItemListView {
5050
@ViewBuilder
5151
var headerView: some View {
5252
VStack {
53-
HStack {
54-
POSHeaderTitleView(title: Localization.title)
55-
if !shouldShowHeaderBanner {
56-
Spacer()
57-
Button(action: {
58-
ServiceLocator.analytics.track(.pointOfSaleSimpleProductsExplanationDialogShown)
59-
showSimpleProductsModal = true
60-
}, label: {
61-
Text(Image(systemName: "info.circle"))
62-
.font(.posButtonSymbolLarge)
63-
})
64-
.foregroundColor(.posOnSurface)
65-
.padding(.trailing, Constants.infoIconPadding)
66-
}
67-
}
53+
POSPageHeaderView(title: Localization.title, trailingContent: {
54+
Button(action: {
55+
ServiceLocator.analytics.track(.pointOfSaleSimpleProductsExplanationDialogShown)
56+
showSimpleProductsModal = true
57+
}, label: {
58+
Text(Image(systemName: "info.circle"))
59+
.font(.posButtonSymbolLarge)
60+
.foregroundStyle(Color.posOnSurface)
61+
.padding(Constants.infoIconInset)
62+
})
63+
.renderedIf(!shouldShowHeaderBanner)
64+
})
6865
if !dynamicTypeSize.isAccessibilitySize, shouldShowHeaderBanner {
6966
bannerCardView
7067
.padding(.horizontal, Constants.bannerCardPadding)
@@ -183,7 +180,7 @@ private extension ItemListView {
183180
static let bannerVerticalPadding: CGFloat = 26
184181
static let bannerTextSpacing: CGFloat = 4
185182
static let bannerTitleSpacing: CGFloat = 8
186-
static let infoIconPadding: CGFloat = 16
183+
static let infoIconInset: EdgeInsets = .init(top: 8, leading: 6, bottom: 8, trailing: 6)
187184
static let iconPadding: CGFloat = 26
188185
static let itemListPadding: CGFloat = 16
189186
static let bannerCardPadding: CGFloat = 16
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import SwiftUI
2+
3+
/// Configuration for the back button in the header.
4+
struct POSPageHeaderBackButtonConfiguration {
5+
enum State {
6+
case enabled
7+
case shimmering
8+
case disabled
9+
}
10+
11+
let state: State
12+
let action: () -> Void
13+
}
14+
15+
/// A header view for POS pages.
16+
/// Design ref: 1qcjzXitBHU7xPnpCOWnNM-fi-450_24951
17+
struct POSPageHeaderView<TrailingContent: View>: View {
18+
private let title: String
19+
private let subtitle: String?
20+
private let backButtonConfiguration: POSPageHeaderBackButtonConfiguration?
21+
private let trailingContent: TrailingContent?
22+
23+
private var hStackAlignment: VerticalAlignment {
24+
subtitle == nil ? .center: .firstTextBaseline
25+
}
26+
27+
private var showsBackButton: Bool {
28+
backButtonConfiguration != nil
29+
}
30+
31+
init(
32+
title: String,
33+
subtitle: String? = nil,
34+
backButtonConfiguration: POSPageHeaderBackButtonConfiguration? = nil,
35+
@ViewBuilder trailingContent: () -> TrailingContent = { EmptyView() }
36+
) {
37+
self.title = title
38+
self.subtitle = subtitle
39+
self.backButtonConfiguration = backButtonConfiguration
40+
self.trailingContent = trailingContent()
41+
}
42+
43+
var body: some View {
44+
HStack(alignment: hStackAlignment, spacing: Constants.horizontalSpacing) {
45+
if showsBackButton {
46+
backButton
47+
}
48+
49+
VStack(alignment: .leading, spacing: Constants.titleSubtitleSpacing) {
50+
Text(title)
51+
.font(.posHeading)
52+
.lineLimit(1)
53+
.minimumScaleFactor(0.5)
54+
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
55+
.foregroundColor(.posOnSurface)
56+
.accessibilityAddTraits(.isHeader)
57+
58+
if let subtitle {
59+
Text(subtitle)
60+
.font(.posBodyLargeRegular())
61+
.lineLimit(1)
62+
.minimumScaleFactor(0.5)
63+
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
64+
.foregroundColor(.posOnSurfaceVariantHighest)
65+
}
66+
}
67+
68+
Spacer()
69+
70+
if let trailingContent {
71+
trailingContent
72+
}
73+
}
74+
.padding(.horizontal, POSHeaderLayoutConstants.sectionHorizontalPadding)
75+
.padding(.vertical, POSHeaderLayoutConstants.sectionVerticalPadding)
76+
}
77+
78+
@ViewBuilder
79+
private var backButton: some View {
80+
if let configuration = backButtonConfiguration {
81+
Button(action: configuration.action) {
82+
Text(Image(systemName: Constants.backButtonIcon))
83+
.font(.posButtonSymbolLarge)
84+
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
85+
.foregroundColor(configuration.state == .disabled ? .posOnSurfaceVariantLowest : .posOnSurface)
86+
.padding(.horizontal, Constants.backButtonHorizontalPadding)
87+
}
88+
.disabled(configuration.state == .disabled)
89+
.if(configuration.state == .shimmering) { view in
90+
view.shimmering()
91+
}
92+
}
93+
}
94+
}
95+
96+
private enum Constants {
97+
static let backButtonIcon = "chevron.backward"
98+
static let backButtonHorizontalPadding: CGFloat = 12
99+
static let horizontalSpacing: CGFloat = 16
100+
static let titleSubtitleSpacing: CGFloat = 4
101+
}
102+
103+
#Preview {
104+
VStack(spacing: 20) {
105+
// Header without back button.
106+
POSPageHeaderView(
107+
title: "Products",
108+
trailingContent: {
109+
Button(action: {}) {
110+
Text(Image(systemName: "info.circle"))
111+
.font(.posButtonSymbolLarge)
112+
}
113+
.foregroundColor(.posOnSurface)
114+
})
115+
116+
// Basic header with back button.
117+
POSPageHeaderView(
118+
title: "Variation",
119+
backButtonConfiguration: .init(state: .enabled, action: {})
120+
)
121+
122+
// Header with shimmering back button.
123+
POSPageHeaderView(
124+
title: "Cart",
125+
backButtonConfiguration: .init(state: .shimmering, action: {})
126+
)
127+
128+
// Header with trailing content.
129+
POSPageHeaderView(
130+
title: "Products",
131+
backButtonConfiguration: .init(state: .enabled, action: {})
132+
) {
133+
HStack(spacing: 16) {
134+
Button(action: {}) {
135+
Text(Image(systemName: "info.circle"))
136+
.font(.posButtonSymbolLarge)
137+
}
138+
.foregroundColor(.posOnSurface)
139+
140+
Button(action: {}) {
141+
Text(Image(systemName: "trash"))
142+
.font(.posButtonSymbolLarge)
143+
}
144+
.foregroundColor(.posOnSurface)
145+
}
146+
}
147+
148+
// Header with subtitle.
149+
POSPageHeaderView(
150+
title: "Cash payment",
151+
subtitle: "Total: $100.00",
152+
backButtonConfiguration: .init(state: .enabled, action: {})
153+
)
154+
155+
// Header with subtitle and disabled back button.
156+
POSPageHeaderView(
157+
title: "Cash payment",
158+
subtitle: "Total: $100.00",
159+
backButtonConfiguration: .init(state: .disabled, action: {})
160+
)
161+
162+
// Header with everything.
163+
POSPageHeaderView(
164+
title: "Title",
165+
subtitle: "Subtitle",
166+
backButtonConfiguration: .init(state: .enabled, action: {})
167+
) {
168+
Button(action: {}) {
169+
Text(Image(systemName: "info.circle"))
170+
.font(.posButtonSymbolLarge)
171+
}
172+
.foregroundColor(.posOnSurface)
173+
}
174+
}
175+
.background(Color.posSurface)
176+
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@
8080
0204E3622B8CD40B00F1B5FD /* WooAnalyticsEvent+Products.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0204E3612B8CD40B00F1B5FD /* WooAnalyticsEvent+Products.swift */; };
8181
0204F0CA29C047A400CFC78F /* SelfSizingHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0204F0C929C047A400CFC78F /* SelfSizingHostingController.swift */; };
8282
0205021E27C8B6C600FB1C6B /* InboxEligibilityUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0205021D27C8B6C600FB1C6B /* InboxEligibilityUseCase.swift */; };
83+
020556512D5DA45500E51059 /* GhostItemCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020556502D5DA45500E51059 /* GhostItemCardView.swift */; };
8384
02055B142D5DAB6400E51059 /* POSCornerRadiusStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02055B132D5DAB6400E51059 /* POSCornerRadiusStyle.swift */; };
8485
020564982D5DC96600E51059 /* POSShadowStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020564972D5DC96600E51059 /* POSShadowStyle.swift */; };
85-
020556512D5DA45500E51059 /* GhostItemCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020556502D5DA45500E51059 /* GhostItemCardView.swift */; };
8686
0206483A23FA4160008441BB /* OrdersRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0206483923FA4160008441BB /* OrdersRootViewController.swift */; };
8787
0206E296299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0206E295299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift */; };
8888
020732042988AB7B000A53C2 /* DomainContactInfoForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020732032988AB7B000A53C2 /* DomainContactInfoForm.swift */; };
@@ -445,6 +445,7 @@
445445
0294F8AB25E8A12C005B537A /* WooTabNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0294F8AA25E8A12C005B537A /* WooTabNavigationController.swift */; };
446446
02952B5127808B08008E9BA3 /* StoreStatsPeriodViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02952B5027808B08008E9BA3 /* StoreStatsPeriodViewModelTests.swift */; };
447447
0295355B245ADF8100BDC42B /* FilterType+Products.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0295355A245ADF8100BDC42B /* FilterType+Products.swift */; };
448+
0295736B2D62B93300865E27 /* POSPageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0295736A2D62B93300865E27 /* POSPageHeaderView.swift */; };
448449
029700EC24FE38C900D242F8 /* ScrollWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029700EB24FE38C900D242F8 /* ScrollWatcher.swift */; };
449450
029700EF24FE38F000D242F8 /* ScrollWatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029700EE24FE38F000D242F8 /* ScrollWatcherTests.swift */; };
450451
029A9C672535873000BECEC5 /* AppCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029A9C662535873000BECEC5 /* AppCoordinatorTests.swift */; };
@@ -3286,9 +3287,9 @@
32863287
0204E3612B8CD40B00F1B5FD /* WooAnalyticsEvent+Products.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+Products.swift"; sourceTree = "<group>"; };
32873288
0204F0C929C047A400CFC78F /* SelfSizingHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingHostingController.swift; sourceTree = "<group>"; };
32883289
0205021D27C8B6C600FB1C6B /* InboxEligibilityUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxEligibilityUseCase.swift; sourceTree = "<group>"; };
3290+
020556502D5DA45500E51059 /* GhostItemCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostItemCardView.swift; sourceTree = "<group>"; };
32893291
02055B132D5DAB6400E51059 /* POSCornerRadiusStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCornerRadiusStyle.swift; sourceTree = "<group>"; };
32903292
020564972D5DC96600E51059 /* POSShadowStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSShadowStyle.swift; sourceTree = "<group>"; };
3291-
020556502D5DA45500E51059 /* GhostItemCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostItemCardView.swift; sourceTree = "<group>"; };
32923293
0206483923FA4160008441BB /* OrdersRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrdersRootViewController.swift; sourceTree = "<group>"; };
32933294
0206E295299CD2C900C061C1 /* WooAnalyticsEvent+DomainSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+DomainSettings.swift"; sourceTree = "<group>"; };
32943295
020732032988AB7B000A53C2 /* DomainContactInfoForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainContactInfoForm.swift; sourceTree = "<group>"; };
@@ -3653,6 +3654,7 @@
36533654
0294F8AA25E8A12C005B537A /* WooTabNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooTabNavigationController.swift; sourceTree = "<group>"; };
36543655
02952B5027808B08008E9BA3 /* StoreStatsPeriodViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreStatsPeriodViewModelTests.swift; sourceTree = "<group>"; };
36553656
0295355A245ADF8100BDC42B /* FilterType+Products.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FilterType+Products.swift"; sourceTree = "<group>"; };
3657+
0295736A2D62B93300865E27 /* POSPageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSPageHeaderView.swift; sourceTree = "<group>"; };
36563658
029700EB24FE38C900D242F8 /* ScrollWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollWatcher.swift; sourceTree = "<group>"; };
36573659
029700EE24FE38F000D242F8 /* ScrollWatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollWatcherTests.swift; sourceTree = "<group>"; };
36583660
029A9C662535873000BECEC5 /* AppCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorTests.swift; sourceTree = "<group>"; };
@@ -6493,6 +6495,7 @@
64936495
20D2CCA22C7E175700051705 /* WavesProgressViewStyle.swift */,
64946496
027ADB722D21812D009608DB /* POSItemImageView.swift */,
64956497
027ADB742D218A8D009608DB /* POSItemCardBorderStylesModifier.swift */,
6498+
0295736A2D62B93300865E27 /* POSPageHeaderView.swift */,
64966499
);
64976500
path = "Reusable Views";
64986501
sourceTree = "<group>";
@@ -16333,6 +16336,7 @@
1633316336
02B7C4F62BE375D800F8E93A /* CollapsibleCustomerCardHeaderView.swift in Sources */,
1633416337
207823E32C5D18CE00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertViewModel.swift in Sources */,
1633516338
021EBB362A3054BE003634CA /* BlazeEligibilityChecker.swift in Sources */,
16339+
0295736B2D62B93300865E27 /* POSPageHeaderView.swift in Sources */,
1633616340
02820F3422C257B700DE0D37 /* UITableView+HeaderFooterHelpers.swift in Sources */,
1633716341
EEC259422B43EF33004D703C /* BlazeEditAdView.swift in Sources */,
1633816342
03E471C0293A158D001A58AD /* CardReaderConnectionAlertsProviding.swift in Sources */,

0 commit comments

Comments
 (0)