Skip to content

Commit 2d7ca07

Browse files
feat: Add UI layer with screens, components and views
1 parent 4dec579 commit 2d7ca07

81 files changed

Lines changed: 12789 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// ApplePayButtonView.swift
3+
//
4+
// Copyright © 2026 Primer API Ltd. All rights reserved.
5+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
6+
7+
import PassKit
8+
import SwiftUI
9+
10+
@available(iOS 15.0, *)
11+
public struct ApplePayButtonView: View {
12+
private let style: PKPaymentButtonStyle
13+
private let type: PKPaymentButtonType
14+
private let cornerRadius: CGFloat
15+
private let action: () -> Void
16+
17+
public init(
18+
style: PKPaymentButtonStyle = .black,
19+
type: PKPaymentButtonType = .plain,
20+
cornerRadius: CGFloat = 8.0,
21+
action: @escaping () -> Void
22+
) {
23+
self.style = style
24+
self.type = type
25+
self.cornerRadius = cornerRadius
26+
self.action = action
27+
}
28+
29+
public var body: some View {
30+
ApplePayButtonRepresentable(
31+
style: style,
32+
type: type,
33+
cornerRadius: cornerRadius,
34+
action: action
35+
)
36+
.frame(height: 50)
37+
.accessibilityLabel("Pay with Apple Pay")
38+
.accessibilityAddTraits(.isButton)
39+
}
40+
}
41+
42+
@available(iOS 15.0, *)
43+
private struct ApplePayButtonRepresentable: UIViewRepresentable {
44+
let style: PKPaymentButtonStyle
45+
let type: PKPaymentButtonType
46+
let cornerRadius: CGFloat
47+
let action: () -> Void
48+
49+
func makeUIView(context: Context) -> PKPaymentButton {
50+
let button = PKPaymentButton(paymentButtonType: type, paymentButtonStyle: style)
51+
button.cornerRadius = cornerRadius
52+
button.addTarget(
53+
context.coordinator, action: #selector(Coordinator.buttonTapped), for: .touchUpInside
54+
)
55+
return button
56+
}
57+
58+
func updateUIView(_ uiView: PKPaymentButton, context: Context) {
59+
uiView.cornerRadius = cornerRadius
60+
}
61+
62+
func makeCoordinator() -> Coordinator {
63+
Coordinator(action: action)
64+
}
65+
66+
final class Coordinator: NSObject {
67+
let action: () -> Void
68+
69+
init(action: @escaping () -> Void) {
70+
self.action = action
71+
}
72+
73+
@objc func buttonTapped() {
74+
action()
75+
}
76+
}
77+
}
78+
79+
#if DEBUG
80+
@available(iOS 15.0, *)
81+
struct ApplePayButtonView_Previews: PreviewProvider {
82+
static var previews: some View {
83+
VStack(spacing: 16) {
84+
ApplePayButtonView(style: .black, type: .plain) {
85+
print("Apple Pay tapped")
86+
}
87+
88+
ApplePayButtonView(style: .white, type: .buy) {
89+
print("Apple Pay tapped")
90+
}
91+
92+
ApplePayButtonView(style: .whiteOutline, type: .checkout) {
93+
print("Apple Pay tapped")
94+
}
95+
96+
ApplePayButtonView(style: .automatic, type: .inStore, cornerRadius: 16) {
97+
print("Apple Pay tapped")
98+
}
99+
}
100+
.padding()
101+
.background(Color.gray.opacity(0.2))
102+
}
103+
}
104+
#endif
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// CardNetworkBadge.swift
3+
//
4+
// Copyright © 2026 Primer API Ltd. All rights reserved.
5+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
6+
7+
import SwiftUI
8+
9+
@available(iOS 15.0, *)
10+
struct CardNetworkBadge: View, LogReporter {
11+
let network: CardNetwork
12+
13+
@Environment(\.designTokens) private var tokens
14+
15+
@ViewBuilder
16+
var body: some View {
17+
if let icon = network.icon {
18+
Image(uiImage: icon)
19+
.resizable()
20+
.aspectRatio(contentMode: .fill)
21+
.frame(
22+
width: PrimerCardNetworkSelector.badgeWidth, height: PrimerCardNetworkSelector.badgeHeight
23+
)
24+
.cornerRadius(PrimerRadius.xsmall(tokens: tokens))
25+
} else {
26+
Text(network.displayName.prefix(2).uppercased())
27+
.font(PrimerFont.smallBadge(tokens: tokens))
28+
.foregroundColor(CheckoutColors.primary(tokens: tokens))
29+
.frame(
30+
width: PrimerCardNetworkSelector.badgeWidth, height: PrimerCardNetworkSelector.badgeHeight
31+
)
32+
.overlay(
33+
RoundedRectangle(cornerRadius: PrimerRadius.xsmall(tokens: tokens))
34+
.stroke(CheckoutColors.borderDefault(tokens: tokens), lineWidth: PrimerBorderWidth.thin)
35+
)
36+
}
37+
}
38+
}
39+
40+
#if DEBUG
41+
@available(iOS 15.0, *)
42+
#Preview("Light Mode") {
43+
VStack(spacing: 16) {
44+
HStack(spacing: 8) {
45+
CardNetworkBadge(network: .visa)
46+
CardNetworkBadge(network: .masterCard)
47+
CardNetworkBadge(network: .amex)
48+
CardNetworkBadge(network: .discover)
49+
}
50+
51+
HStack(spacing: 8) {
52+
CardNetworkBadge(network: .cartesBancaires)
53+
CardNetworkBadge(network: .diners)
54+
CardNetworkBadge(network: .jcb)
55+
CardNetworkBadge(network: .unknown)
56+
}
57+
}
58+
.padding()
59+
.environment(\.designTokens, MockDesignTokens.light)
60+
}
61+
62+
@available(iOS 15.0, *)
63+
#Preview("Dark Mode") {
64+
VStack(spacing: 16) {
65+
HStack(spacing: 8) {
66+
CardNetworkBadge(network: .visa)
67+
CardNetworkBadge(network: .masterCard)
68+
CardNetworkBadge(network: .amex)
69+
CardNetworkBadge(network: .discover)
70+
}
71+
72+
HStack(spacing: 8) {
73+
CardNetworkBadge(network: .cartesBancaires)
74+
CardNetworkBadge(network: .diners)
75+
CardNetworkBadge(network: .jcb)
76+
CardNetworkBadge(network: .unknown)
77+
}
78+
}
79+
.padding()
80+
.background(Color.black)
81+
.environment(\.designTokens, MockDesignTokens.dark)
82+
.preferredColorScheme(.dark)
83+
}
84+
#endif
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//
2+
// CheckoutHeaderView.swift
3+
//
4+
// Copyright © 2026 Primer API Ltd. All rights reserved.
5+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
6+
7+
import SwiftUI
8+
9+
@available(iOS 15.0, *)
10+
struct CheckoutHeaderView: View {
11+
let showBackButton: Bool
12+
let onBack: () -> Void
13+
let rightButton: RightButtonConfig?
14+
15+
@Environment(\.designTokens) private var tokens
16+
17+
struct RightButtonConfig {
18+
let title: String
19+
let icon: String?
20+
let action: () -> Void
21+
let accessibilityIdentifier: String
22+
let accessibilityLabel: String
23+
24+
static func closeButton(action: @escaping () -> Void) -> RightButtonConfig {
25+
RightButtonConfig(
26+
title: CheckoutComponentsStrings.cancelButton,
27+
icon: nil,
28+
action: action,
29+
accessibilityIdentifier: AccessibilityIdentifiers.Common.closeButton,
30+
accessibilityLabel: CheckoutComponentsStrings.a11yCancel
31+
)
32+
}
33+
34+
static func editButton(action: @escaping () -> Void) -> RightButtonConfig {
35+
RightButtonConfig(
36+
title: CheckoutComponentsStrings.editButton,
37+
icon: "pencil",
38+
action: action,
39+
accessibilityIdentifier: AccessibilityIdentifiers.Common.editButton,
40+
accessibilityLabel: CheckoutComponentsStrings.a11yEdit
41+
)
42+
}
43+
44+
static func doneButton(action: @escaping () -> Void) -> RightButtonConfig {
45+
RightButtonConfig(
46+
title: CheckoutComponentsStrings.doneButton,
47+
icon: "checkmark",
48+
action: action,
49+
accessibilityIdentifier: AccessibilityIdentifiers.Common.doneButton,
50+
accessibilityLabel: CheckoutComponentsStrings.a11yDone
51+
)
52+
}
53+
}
54+
55+
init(
56+
showBackButton: Bool = true,
57+
onBack: @escaping () -> Void,
58+
rightButton: RightButtonConfig? = nil
59+
) {
60+
self.showBackButton = showBackButton
61+
self.onBack = onBack
62+
self.rightButton = rightButton
63+
}
64+
65+
var body: some View {
66+
HStack {
67+
if showBackButton {
68+
makeBackButtonView()
69+
}
70+
71+
Spacer()
72+
73+
if let rightButton {
74+
makeRightButtonView(config: rightButton)
75+
}
76+
}
77+
.padding(.horizontal, PrimerSpacing.large(tokens: tokens))
78+
.padding(.vertical, PrimerSpacing.medium(tokens: tokens))
79+
}
80+
81+
private func makeBackButtonView() -> some View {
82+
Button(action: onBack) {
83+
HStack(spacing: PrimerSpacing.xsmall(tokens: tokens)) {
84+
Image(systemName: RTLIcon.backChevron)
85+
Text(CheckoutComponentsStrings.backButton)
86+
}
87+
.font(PrimerFont.bodyMedium(tokens: tokens))
88+
.foregroundColor(CheckoutColors.textPrimary(tokens: tokens))
89+
}
90+
.accessibility(
91+
config: AccessibilityConfiguration(
92+
identifier: AccessibilityIdentifiers.Common.backButton,
93+
label: CheckoutComponentsStrings.a11yBack,
94+
traits: [.isButton]
95+
)
96+
)
97+
}
98+
99+
private func makeRightButtonView(config: RightButtonConfig) -> some View {
100+
Button(action: config.action) {
101+
HStack(spacing: PrimerSpacing.xsmall(tokens: tokens)) {
102+
if let icon = config.icon {
103+
Image(systemName: icon)
104+
.font(PrimerFont.caption(tokens: tokens))
105+
}
106+
Text(config.title)
107+
.font(PrimerFont.titleLarge(tokens: tokens))
108+
}
109+
.foregroundColor(CheckoutColors.textPrimary(tokens: tokens))
110+
}
111+
.accessibility(
112+
config: AccessibilityConfiguration(
113+
identifier: config.accessibilityIdentifier,
114+
label: config.accessibilityLabel,
115+
traits: [.isButton]
116+
)
117+
)
118+
}
119+
}

0 commit comments

Comments
 (0)