diff --git a/.swiftlint.yml b/.swiftlint.yml index 02769e0..19f6427 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,3 +3,23 @@ excluded: # paths to ignore during linting. Takes precedence over `included`. - "*/*/GeneratedMocks.swift" - "*/*/*.generated.swift" - ".build/" + +custom_rules: + paystack_ui_no_literal_colour: + name: "PaystackUI literal colour" + regex: "(Color\\.(white|black|gray|red|blue|green)\\b|Color\\(red:\\s|UIColor\\(red:\\s)" + message: "Use a semantic token from Design/Colors.swift instead of a literal colour. Fixed exceptions (e.g. the QR quiet-zone plate) must carry a `// swiftlint:disable:next paystack_ui_no_literal_colour` comment explaining why." + included: "Sources/PaystackUI/.*\\.swift" + excluded: + - "Sources/PaystackUI/Design/Colors.swift" + - "Sources/PaystackUI/Design/DarkModePreviews.swift" + severity: warning + paystack_ui_no_hue_token: + name: "PaystackUI legacy hue token" + regex: "Color\\.(stackBlue|navy0[2-5]|stackGreen|green02|gray01|error0[12]|warning0[1245])\\b" + message: "Use the semantic token layer (contentPrimary, borderSubtle, accentPrimary, …) instead of the underlying hue token. Hue tokens are kept as internal primitives per Q8 in DARK_MODE_IMPLEMENTATION_PLAN.md and should not be called directly by view code." + included: "Sources/PaystackUI/.*\\.swift" + excluded: + - "Sources/PaystackUI/Design/Colors.swift" + - "Sources/PaystackUI/Design/DarkModePreviews.swift" + severity: warning diff --git a/Sources/PaystackUI/Charge/BankTransfer/Views/BankPickerSheet.swift b/Sources/PaystackUI/Charge/BankTransfer/Views/BankPickerSheet.swift index 926018a..2e88d1f 100644 --- a/Sources/PaystackUI/Charge/BankTransfer/Views/BankPickerSheet.swift +++ b/Sources/PaystackUI/Charge/BankTransfer/Views/BankPickerSheet.swift @@ -14,7 +14,7 @@ struct BankPickerSheet: View { header Divider() - .background(Color.navy05) + .background(Color.borderPrimary) ScrollView { LazyVStack(spacing: 0) { @@ -24,24 +24,26 @@ struct BankPickerSheet: View { isSelected: slug == currentSlug, onTap: { onSelect(slug) }) Divider() - .background(Color.navy05) + .background(Color.borderPrimary) } } } Spacer(minLength: 0) } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.surfacePrimary.ignoresSafeArea()) } private var header: some View { HStack { Text("Change bank") .font(.heading3) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) Spacer() Button(action: onCancel) { Image(systemName: "xmark") - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) } } .padding(.doublePadding) @@ -60,11 +62,11 @@ private struct BankPickerRow: View { HStack { Text(displayName) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) Spacer() if isSelected { Image(systemName: "checkmark") - .foregroundColor(.stackGreen) + .foregroundColor(.accentPrimary) } } .padding(.doublePadding) diff --git a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferAccountDetailsView.swift b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferAccountDetailsView.swift index e00fadd..ab0d408 100644 --- a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferAccountDetailsView.swift +++ b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferAccountDetailsView.swift @@ -31,7 +31,7 @@ struct BankTransferAccountDetailsView: View { Text("Transfer exactly \(amount.description) to") .font(.heading3) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) accountCard @@ -42,7 +42,7 @@ struct BankTransferAccountDetailsView: View { .buttonStyle(PrimaryButtonStyle(showLoading: false)) Button("Change payment method", action: onChangePaymentMethod) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) .padding(.top, .singlePadding) } @@ -74,13 +74,13 @@ struct BankTransferAccountDetailsView: View { } .overlay( RoundedRectangle(cornerRadius: .cornerRadius) - .stroke(Color.navy05, lineWidth: 1) + .stroke(Color.borderPrimary, lineWidth: 1) ) } private var divider: some View { Rectangle() - .fill(Color.navy05) + .fill(Color.surfaceSecondary) .frame(height: 1) } @@ -88,15 +88,15 @@ struct BankTransferAccountDetailsView: View { VStack(spacing: .singlePadding) { Text("This account is for one-time use only and expires in") .font(.body14R) - .foregroundColor(.navy03) + .foregroundColor(.contentTertiary) .multilineTextAlignment(.center) Text(formattedRemaining) .font(.body14M) - .foregroundColor(.stackGreen) + .foregroundColor(.accentPrimary) ProgressView(value: progress) .progressViewStyle(.linear) - .accentColor(.stackGreen) + .accentColor(.accentPrimary) } } diff --git a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferConfirmingView.swift b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferConfirmingView.swift index 4974da2..d832e05 100644 --- a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferConfirmingView.swift +++ b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferConfirmingView.swift @@ -43,7 +43,7 @@ struct BankTransferConfirmingView: View { Text(bodyCopy) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) .animation(.easeInOut(duration: 0.25), value: phase) @@ -54,11 +54,11 @@ struct BankTransferConfirmingView: View { .disabled(true) Button("Back to account number", action: onBackToAccountNumber) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) Button("Change payment method", action: onChangePaymentMethod) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) } .padding(.doublePadding) @@ -92,10 +92,10 @@ struct BankTransferConfirmingView: View { GeometryReader { geo in ZStack(alignment: .leading) { Rectangle() - .fill(Color.gray01) + .fill(Color.trackInactive) .frame(height: 1) Rectangle() - .fill(Color.stackGreen) + .fill(Color.accentPrimary) .frame(width: receivedByBackend ? geo.size.width : 0, height: 1) .animation(.easeInOut(duration: 0.45), value: receivedByBackend) diff --git a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferDelayedConfirmationView.swift b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferDelayedConfirmationView.swift index 69c49d9..60b1bf8 100644 --- a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferDelayedConfirmationView.swift +++ b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferDelayedConfirmationView.swift @@ -12,12 +12,12 @@ struct BankTransferDelayedConfirmationView: View { Text("We'll complete this transaction automatically once we confirm your transfer.") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) Text("If you have any issues with this transfer, please contact us via \(supportEmail) with the following details:") .font(.body14R) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) VStack(alignment: .leading, spacing: .singlePadding) { @@ -33,7 +33,7 @@ struct BankTransferDelayedConfirmationView: View { .buttonStyle(SecondaryButtonStyle()) Button("Keep waiting", action: onKeepWaiting) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) } .padding(.doublePadding) @@ -49,10 +49,10 @@ private struct NumberedListRow: View { HStack(spacing: .singlePadding) { Text("\(index).") .font(.body14R) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) Text(text) .font(.body14R) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) } } } diff --git a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferRefundInitiatedView.swift b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferRefundInitiatedView.swift index 3132e24..7f7cc43 100644 --- a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferRefundInitiatedView.swift +++ b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferRefundInitiatedView.swift @@ -17,12 +17,12 @@ struct BankTransferRefundInitiatedView: View { Text("Refund initiated") .font(.heading2) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) Text(message) .font(.body14R) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .multilineTextAlignment(.center) .padding(.horizontal, .singlePadding) @@ -31,7 +31,7 @@ struct BankTransferRefundInitiatedView: View { .buttonStyle(PrimaryButtonStyle(showLoading: false)) Button("Close", action: dismissWithError) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) } .padding(.doublePadding) diff --git a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferTakingLongerView.swift b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferTakingLongerView.swift index 5c52538..d56f9cd 100644 --- a/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferTakingLongerView.swift +++ b/Sources/PaystackUI/Charge/BankTransfer/Views/BankTransferTakingLongerView.swift @@ -14,7 +14,7 @@ struct BankTransferTakingLongerView: View { Text("It's taking longer than expected to confirm your transfer. " + "You don't have to wait here till we confirm it.") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) timeline @@ -23,11 +23,11 @@ struct BankTransferTakingLongerView: View { .buttonStyle(PrimaryButtonStyle(showLoading: false)) Button("Back to account number", action: onBackToAccountNumber) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) Button("Change payment method", action: onChangePaymentMethod) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) } .padding(.doublePadding) @@ -37,7 +37,7 @@ struct BankTransferTakingLongerView: View { HStack(spacing: 0) { TimelineNode(label: "Sent", state: .complete) Rectangle() - .fill(Color.gray01) + .fill(Color.trackInactive) .frame(height: 1) .padding(.horizontal, .singlePadding) TimelineNode(label: "Received", state: .pending) diff --git a/Sources/PaystackUI/Charge/ChargeCard/Address/CardAddressView.swift b/Sources/PaystackUI/Charge/ChargeCard/Address/CardAddressView.swift index c2fa74a..bc81637 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/Address/CardAddressView.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/Address/CardAddressView.swift @@ -24,7 +24,7 @@ struct CardAddressView: View { Text(displayMessage) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) FormInput(title: "Complete Payment", diff --git a/Sources/PaystackUI/Charge/ChargeCard/Birthday/CardBirthdayView.swift b/Sources/PaystackUI/Charge/ChargeCard/Birthday/CardBirthdayView.swift index 7e1878e..0ecc6b2 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/Birthday/CardBirthdayView.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/Birthday/CardBirthdayView.swift @@ -31,7 +31,7 @@ struct CardBirthdayView: View { Text(displayMessage) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) FormInput(title: "Authorize", diff --git a/Sources/PaystackUI/Charge/ChargeCard/OTP/CardOTPVIew.swift b/Sources/PaystackUI/Charge/ChargeCard/OTP/CardOTPVIew.swift index 4727143..f0b82be 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/OTP/CardOTPVIew.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/OTP/CardOTPVIew.swift @@ -23,7 +23,7 @@ struct CardOTPVIew: View { Text(displayMessage) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) FormInput(title: "Authorize", diff --git a/Sources/PaystackUI/Charge/ChargeCard/Phone/CardPhoneView.swift b/Sources/PaystackUI/Charge/ChargeCard/Phone/CardPhoneView.swift index 5170a71..0474200 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/Phone/CardPhoneView.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/Phone/CardPhoneView.swift @@ -26,7 +26,7 @@ struct CardPhoneView: View { Text(displayMessage) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) FormInput(title: "Send OTP", diff --git a/Sources/PaystackUI/Charge/ChargeCard/Pin/CardPinView.swift b/Sources/PaystackUI/Charge/ChargeCard/Pin/CardPinView.swift index 0d07378..8e72916 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/Pin/CardPinView.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/Pin/CardPinView.swift @@ -24,7 +24,7 @@ struct CardPinView: View { onCommit: viewModel.submitPin) Button("Cancel", action: viewModel.cancelTransaction) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) } #endif @@ -36,12 +36,12 @@ struct CardPinView: View { var titleText: some View { Text("Please enter your 4-digit card pin to authorize this payment") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) } var loadingView: some View { - LoadingIndicator(tintColor: .navy04) + LoadingIndicator(tintColor: .spinnerTint) .scaleEffect(2) .padding(.doublePadding) } diff --git a/Sources/PaystackUI/Charge/ChargeCard/Redirect/CardRedirectView.swift b/Sources/PaystackUI/Charge/ChargeCard/Redirect/CardRedirectView.swift index dc88699..47f3b12 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/Redirect/CardRedirectView.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/Redirect/CardRedirectView.swift @@ -33,7 +33,7 @@ struct CardRedirectView: View { isLoading: $webviewLoading) if webviewLoading { - LoadingIndicator(tintColor: .navy04) + LoadingIndicator(tintColor: .spinnerTint) .scaleEffect(2) } } @@ -52,7 +52,7 @@ struct CardRedirectView: View { var titleText: some View { Text("Please click the button below to authenticate with your bank") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) } @@ -65,7 +65,7 @@ struct CardRedirectView: View { var cancelButton: some View { Button("Cancel", action: viewModel.cancelTransaction) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) } diff --git a/Sources/PaystackUI/Charge/ChargeCard/TestMode/TestModeCardSelectionView.swift b/Sources/PaystackUI/Charge/ChargeCard/TestMode/TestModeCardSelectionView.swift index 9c51158..f8c41d3 100644 --- a/Sources/PaystackUI/Charge/ChargeCard/TestMode/TestModeCardSelectionView.swift +++ b/Sources/PaystackUI/Charge/ChargeCard/TestMode/TestModeCardSelectionView.swift @@ -21,7 +21,7 @@ struct TestModeCardSelectionView: View { VStack(spacing: .triplePadding) { Text("Use any of the options below to test the payment flow") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) FormInput(title: viewModel.buttonTitle, @@ -42,7 +42,7 @@ struct TestModeCardSelectionView: View { selectedItem: $viewModel.testCard) { item in HStack { Text(item.description) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .font(.body14R) Spacer() diff --git a/Sources/PaystackUI/Charge/ChargeView.swift b/Sources/PaystackUI/Charge/ChargeView.swift index 729d181..be0df9d 100644 --- a/Sources/PaystackUI/Charge/ChargeView.swift +++ b/Sources/PaystackUI/Charge/ChargeView.swift @@ -47,6 +47,8 @@ struct ChargeView: View { .aspectRatio(contentMode: .fit) .frame(width: 140) } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.surfacePrimary.ignoresSafeArea()) .task(viewModel.verifyAccessCodeAndProceed) .modalCancelButton(showConfirmation: viewModel.displayCloseButtonConfirmation, onCancelled: chargeCancelled) diff --git a/Sources/PaystackUI/Charge/MobileMoney/Views/ChannelSelectionView.swift b/Sources/PaystackUI/Charge/MobileMoney/Views/ChannelSelectionView.swift index 679fa93..7f954d6 100644 --- a/Sources/PaystackUI/Charge/MobileMoney/Views/ChannelSelectionView.swift +++ b/Sources/PaystackUI/Charge/MobileMoney/Views/ChannelSelectionView.swift @@ -23,7 +23,7 @@ struct ChannelSelectionView: View { Text("Choose a payment method to continue") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) VStack(spacing: .singlePadding) { @@ -84,7 +84,7 @@ struct ChannelView: View { Text(channelTitle) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .lineLimit(1) .minimumScaleFactor(0.85) @@ -96,7 +96,7 @@ struct ChannelView: View { .cornerRadius(.cornerRadius) .overlay( RoundedRectangle(cornerRadius: .cornerRadius) - .stroke(Color.navy05, lineWidth: 1) + .stroke(Color.borderPrimary, lineWidth: 1) ) } } diff --git a/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyChargeView.swift b/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyChargeView.swift index bf05e5a..8c3b6e9 100644 --- a/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyChargeView.swift +++ b/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyChargeView.swift @@ -39,7 +39,7 @@ struct MobileMoneyChargeView: View { Text("Please enter the mobile money number to begin this payment") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) FormInput(title: "Pay \(viewModel.transactionDetails.amountCurrency.description)", diff --git a/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyProcessingView.swift b/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyProcessingView.swift index efd726f..a8b17ab 100644 --- a/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyProcessingView.swift +++ b/Sources/PaystackUI/Charge/MobileMoney/Views/MobileMoneyProcessingView.swift @@ -20,12 +20,12 @@ struct MobileMoneyProcessingView: View { Image.otpIcon Text(viewModel.mobileMoneyTransaction.phone) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) Text(viewModel.authorizationPromptText) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) CountdownView(counter: $viewModel.counter, @@ -63,7 +63,7 @@ struct ProgressTrack: View { .fill(Color.clear) .frame(width: 35, height: 35) .overlay( - Circle().stroke(Color.gray01, lineWidth: 5) + Circle().stroke(Color.trackInactive, lineWidth: 5) ) } } @@ -86,7 +86,7 @@ struct ProgressBar: View { ) ) .foregroundColor( - (completed() ? Color.stackGreen : Color.stackGreen) + (completed() ? Color.accentPrimary : Color.accentPrimary) ).animation( .easeInOut(duration: 0.2) ) @@ -115,7 +115,7 @@ struct CountdownView: View { } Text("Payment is valid for \(counterToMinutes())") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) }.onReceive(timer) { _ in if self.counter < self.countTo { diff --git a/Sources/PaystackUI/Charge/Success/ChargeSuccessView.swift b/Sources/PaystackUI/Charge/Success/ChargeSuccessView.swift index c17babe..4a714d5 100644 --- a/Sources/PaystackUI/Charge/Success/ChargeSuccessView.swift +++ b/Sources/PaystackUI/Charge/Success/ChargeSuccessView.swift @@ -16,11 +16,11 @@ struct ChargeSuccessView: View { Text("Payment Successful") .font(.heading3) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) Text("You paid \(amount.description) to \(merchant)") .font(.body16R) - .foregroundColor(.navy03) + .foregroundColor(.contentTertiary) } .task(dismissAutomatically) } diff --git a/Sources/PaystackUI/Charge/Zap/Components/QRCodeImage.swift b/Sources/PaystackUI/Charge/Zap/Components/QRCodeImage.swift index b3be795..5016d80 100644 --- a/Sources/PaystackUI/Charge/Zap/Components/QRCodeImage.swift +++ b/Sources/PaystackUI/Charge/Zap/Components/QRCodeImage.swift @@ -25,7 +25,7 @@ struct QRCodeImage: View { .transition(.opacity) } else if loadFailed { Image(systemName: "exclamationmark.triangle") - .foregroundColor(.warning02) + .foregroundColor(.contentWarning) } else { ShimmerPlaceholder() } @@ -66,12 +66,12 @@ struct ShimmerPlaceholder: View { var body: some View { GeometryReader { geo in ZStack { - Color.gray01 + Color.shimmerBase LinearGradient( gradient: Gradient(stops: [ - .init(color: Color.white.opacity(0.0), location: 0.0), - .init(color: Color.white.opacity(0.5), location: 0.5), - .init(color: Color.white.opacity(0.0), location: 1.0) + .init(color: Color.shimmerHighlight.opacity(0.0), location: 0.0), + .init(color: Color.shimmerHighlight.opacity(0.5), location: 0.5), + .init(color: Color.shimmerHighlight.opacity(0.0), location: 1.0) ]), startPoint: .topLeading, endPoint: .bottomTrailing) diff --git a/Sources/PaystackUI/Charge/Zap/Views/ZapPaymentView.swift b/Sources/PaystackUI/Charge/Zap/Views/ZapPaymentView.swift index 566bb1e..4401019 100644 --- a/Sources/PaystackUI/Charge/Zap/Views/ZapPaymentView.swift +++ b/Sources/PaystackUI/Charge/Zap/Views/ZapPaymentView.swift @@ -20,7 +20,7 @@ struct ZapPaymentView: View { } private var countdownValueColor: Color { - remainingSeconds <= 60 ? .warning02 : .stackGreen + remainingSeconds <= 60 ? .contentWarning : .accentPrimary } var body: some View { @@ -31,7 +31,7 @@ struct ZapPaymentView: View { Text("Open Zap or scan this QR code to complete this payment") .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) qrCodeBlock @@ -58,17 +58,17 @@ struct ZapPaymentView: View { QRCodeImage(url: details.qrImageURL) .frame(width: 220, height: 220) .padding(.singlePadding) - .background(Color.white) + .background(Color.qrPlate) .overlay( RoundedRectangle(cornerRadius: .cornerRadius) - .stroke(Color.navy05, lineWidth: 1)) + .stroke(Color.borderPrimary, lineWidth: 1)) } private var expiresInRow: some View { HStack(spacing: 4) { Text("Expires in") .font(.body14M) - .foregroundColor(.navy03) + .foregroundColor(.contentTertiary) Text(formattedRemaining) .font(.body14M) .foregroundColor(countdownValueColor) @@ -84,7 +84,7 @@ struct ZapPaymentView: View { } Button("Change payment method", action: onChangePaymentMethod) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14M) .padding(.top, .singlePadding) } diff --git a/Sources/PaystackUI/Charge/Zap/Views/ZapSessionExpiredView.swift b/Sources/PaystackUI/Charge/Zap/Views/ZapSessionExpiredView.swift index ec247ad..c677514 100644 --- a/Sources/PaystackUI/Charge/Zap/Views/ZapSessionExpiredView.swift +++ b/Sources/PaystackUI/Charge/Zap/Views/ZapSessionExpiredView.swift @@ -13,12 +13,12 @@ struct ZapSessionExpiredView: View { Text("Session expired") .font(.heading2) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .multilineTextAlignment(.center) Text(message) .font(.body14R) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .multilineTextAlignment(.center) .padding(.horizontal, .singlePadding) diff --git a/Sources/PaystackUI/Components/AccountDetailRow.swift b/Sources/PaystackUI/Components/AccountDetailRow.swift index d6045cc..8af47c1 100644 --- a/Sources/PaystackUI/Components/AccountDetailRow.swift +++ b/Sources/PaystackUI/Components/AccountDetailRow.swift @@ -26,11 +26,11 @@ struct AccountDetailRow: View { VStack(alignment: .leading, spacing: .quarterPadding) { Text(label) .font(.body12M) - .foregroundColor(.navy03) + .foregroundColor(.contentTertiary) .padding(.bottom, .singlePadding) Text(value) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) } Spacer() trailingView @@ -47,7 +47,7 @@ struct AccountDetailRow: View { #if os(iOS) Button(action: copy) { Image(systemName: justCopied ? "checkmark" : "doc.on.doc") - .foregroundColor(justCopied ? .stackGreen : .navy02) + .foregroundColor(justCopied ? .accentPrimary : .contentSecondary) } #else EmptyView() @@ -55,7 +55,7 @@ struct AccountDetailRow: View { case .text(let title, let action): Button(title, action: action) .font(.body14M) - .foregroundColor(.stackGreen) + .foregroundColor(.accentPrimary) } } diff --git a/Sources/PaystackUI/Components/ChangePaymentMethodFooter.swift b/Sources/PaystackUI/Components/ChangePaymentMethodFooter.swift index 83e7ac6..0880944 100644 --- a/Sources/PaystackUI/Components/ChangePaymentMethodFooter.swift +++ b/Sources/PaystackUI/Components/ChangePaymentMethodFooter.swift @@ -12,11 +12,11 @@ struct ChangePaymentMethodFooter: View { Text("Change payment method") } .font(.body14M) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .padding(.horizontal, .doublePadding) .padding(.vertical, .singlePadding) .overlay( - Capsule().stroke(Color.navy05, lineWidth: 1) + Capsule().stroke(Color.borderPrimary, lineWidth: 1) ) } .padding(.bottom, .doublePadding) diff --git a/Sources/PaystackUI/Components/ErrorView.swift b/Sources/PaystackUI/Components/ErrorView.swift index fbdd7f8..a2bc341 100644 --- a/Sources/PaystackUI/Components/ErrorView.swift +++ b/Sources/PaystackUI/Components/ErrorView.swift @@ -27,7 +27,7 @@ struct ErrorView: View { Text(message) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .padding(.bottom, .singlePadding) if let buttonText, let buttonAction { diff --git a/Sources/PaystackUI/Components/Form/FormInput.swift b/Sources/PaystackUI/Components/Form/FormInput.swift index 874c132..1b3d4c0 100644 --- a/Sources/PaystackUI/Components/Form/FormInput.swift +++ b/Sources/PaystackUI/Components/Form/FormInput.swift @@ -66,7 +66,7 @@ struct FormInput) -> some View { configuration .padding(.doublePadding) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) .font(.body16R) } } diff --git a/Sources/PaystackUI/Components/Form/PickerFormInputView.swift b/Sources/PaystackUI/Components/Form/PickerFormInputView.swift index 23afa75..77e0b7c 100644 --- a/Sources/PaystackUI/Components/Form/PickerFormInputView.swift +++ b/Sources/PaystackUI/Components/Form/PickerFormInputView.swift @@ -38,7 +38,7 @@ struct PickerFormInputView: FormInputI .frame(maxWidth: .infinity) .background( RoundedRectangle(cornerRadius: .cornerRadius, style: .continuous) - .stroke(Color.navy04, lineWidth: 1) + .stroke(Color.borderStrong, lineWidth: 1) ) .form(title) } @@ -47,10 +47,10 @@ struct PickerFormInputView: FormInputI HStack { if let selectedItem = selectedItem { Text(selectedItem.description) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) } else { Text(placeholder) - .foregroundColor(.navy04) + .foregroundColor(.contentPlaceholder) } Spacer() diff --git a/Sources/PaystackUI/Components/Form/TextFieldFocusedBorder.swift b/Sources/PaystackUI/Components/Form/TextFieldFocusedBorder.swift index 711d239..b03aa36 100644 --- a/Sources/PaystackUI/Components/Form/TextFieldFocusedBorder.swift +++ b/Sources/PaystackUI/Components/Form/TextFieldFocusedBorder.swift @@ -13,7 +13,7 @@ struct TextFieldFocusedBorder: ViewModifier { .focused($isFocused) .background( RoundedRectangle(cornerRadius: .cornerRadius, style: .continuous) - .stroke(isFocused ? Color.stackGreen : defaultColor, lineWidth: 1) + .stroke(isFocused ? Color.accentPrimary : defaultColor, lineWidth: 1) ) } } @@ -21,7 +21,7 @@ struct TextFieldFocusedBorder: ViewModifier { extension View { @ViewBuilder - func focusedBorderColor(defaultColor: Color = .navy04) -> some View { + func focusedBorderColor(defaultColor: Color = .borderStrong) -> some View { if #available(iOS 15, macOS 12.0, *) { self.modifier(TextFieldFocusedBorder(defaultColor: defaultColor)) } else { diff --git a/Sources/PaystackUI/Components/Form/TextFieldFormInputView.swift b/Sources/PaystackUI/Components/Form/TextFieldFormInputView.swift index 92e6f57..c5bc742 100644 --- a/Sources/PaystackUI/Components/Form/TextFieldFormInputView.swift +++ b/Sources/PaystackUI/Components/Form/TextFieldFormInputView.swift @@ -62,7 +62,7 @@ struct TextFieldFormInputView: FormInputItemView { } } .textFieldStyle(.form) - .focusedBorderColor(defaultColor: inErrorState ? .error02 : .navy04) + .focusedBorderColor(defaultColor: inErrorState ? .borderError : .borderStrong) .setDefaultFocus(defaultFocused) .keyboardType(keyboardType) .onReceive(Just(text)) { value in diff --git a/Sources/PaystackUI/Components/Form/TitleFormInputView.swift b/Sources/PaystackUI/Components/Form/TitleFormInputView.swift index 4d8ddb0..35d916d 100644 --- a/Sources/PaystackUI/Components/Form/TitleFormInputView.swift +++ b/Sources/PaystackUI/Components/Form/TitleFormInputView.swift @@ -10,7 +10,7 @@ struct TitleFormInputView: FormInputItemViewModifier { var body: some View { VStack(alignment: .leading, spacing: .halfPadding) { Text(title) - .foregroundColor(.navy02) + .foregroundColor(.contentSecondary) .font(.body14R) content diff --git a/Sources/PaystackUI/Components/Form/Validation/ValidationFormInputView.swift b/Sources/PaystackUI/Components/Form/Validation/ValidationFormInputView.swift index eeaa33c..aace4f7 100644 --- a/Sources/PaystackUI/Components/Form/Validation/ValidationFormInputView.swift +++ b/Sources/PaystackUI/Components/Form/Validation/ValidationFormInputView.swift @@ -37,7 +37,7 @@ struct ValidationFormInputView: FormInputItemViewMod if !validatorStates[index] { Text(validator.errorMessage) .font(.body12R) - .foregroundColor(.error01) + .foregroundColor(.contentError) } } } diff --git a/Sources/PaystackUI/Components/LoadingIndicator.swift b/Sources/PaystackUI/Components/LoadingIndicator.swift index 449950a..92e66ca 100644 --- a/Sources/PaystackUI/Components/LoadingIndicator.swift +++ b/Sources/PaystackUI/Components/LoadingIndicator.swift @@ -5,7 +5,7 @@ struct LoadingIndicator: View { var tintColor: Color - init(tintColor: Color = .white) { + init(tintColor: Color = .contentOnAccent) { self.tintColor = tintColor } diff --git a/Sources/PaystackUI/Components/LoadingView.swift b/Sources/PaystackUI/Components/LoadingView.swift index 99cc3e8..dad82b1 100644 --- a/Sources/PaystackUI/Components/LoadingView.swift +++ b/Sources/PaystackUI/Components/LoadingView.swift @@ -17,9 +17,9 @@ struct LoadingView: View { VStack(spacing: .quadPadding) { Text(message) .font(.body16M) - .foregroundColor(.stackBlue) + .foregroundColor(.contentPrimary) - LoadingIndicator(tintColor: .navy04) + LoadingIndicator(tintColor: .spinnerTint) .scaleEffect(2) .padding(.bottom, .triplePadding) } diff --git a/Sources/PaystackUI/Components/ModalCancelButton.swift b/Sources/PaystackUI/Components/ModalCancelButton.swift index 2571690..9ccc5eb 100644 --- a/Sources/PaystackUI/Components/ModalCancelButton.swift +++ b/Sources/PaystackUI/Components/ModalCancelButton.swift @@ -35,7 +35,7 @@ struct ModalCancelButton: ViewModifier { Button(action: closeButtonTapped) { Image(systemName: "xmark") .font(.system(size: 20, weight: .medium)) - .foregroundColor(.gray01) + .foregroundColor(.iconMuted) } } diff --git a/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextView.swift b/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextView.swift index 916770f..1162296 100644 --- a/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextView.swift +++ b/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextView.swift @@ -78,11 +78,11 @@ struct PinTextView: View { otpBackgroundColor: UIColor = .clear, otpFilledBackgroundColor: UIColor = .clear, otpCornerRaduis: CGFloat = 3, - otpDefaultBorderColor: UIColor = UIColor(red: 0.89, green: 0.89, blue: 0.89, alpha: 1), - otpFilledBorderColor: UIColor = UIColor(.stackGreen), + otpDefaultBorderColor: UIColor = .pinSlotBorder, + otpFilledBorderColor: UIColor = .accentPrimary, otpDefaultBorderWidth: CGFloat = 1, otpFilledBorderWidth: CGFloat = 1, - otpTextColor: UIColor = UIColor(.stackBlue), + otpTextColor: UIColor = .contentPrimary, otpFontSize: CGFloat = 18, otpFont: UIFont = .heading2, isSecureTextEntry: Bool = false, diff --git a/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextViewRepresentable.swift b/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextViewRepresentable.swift index 8d2b6d4..921767a 100644 --- a/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextViewRepresentable.swift +++ b/Sources/PaystackUI/Components/PinTextView/SwiftUI/Core/PinTextViewRepresentable.swift @@ -25,8 +25,8 @@ struct PinTextViewRepresentable: UIViewRepresentable { text: Binding, slotsCount: Int = 4, otpDefaultCharacter: String = "", - otpBackgroundColor: UIColor = UIColor(red: 0.949, green: 0.949, blue: 0.949, alpha: 1), - otpFilledBackgroundColor: UIColor = UIColor(red: 0.949, green: 0.949, blue: 0.949, alpha: 1), + otpBackgroundColor: UIColor = .pinSlotSurface, + otpFilledBackgroundColor: UIColor = .pinSlotSurface, otpCornerRaduis: CGFloat = 3, otpDefaultBorderColor: UIColor = .gray, otpFilledBorderColor: UIColor = .green, diff --git a/Sources/PaystackUI/Components/PinTextView/UIKit/Core/PinTextField.swift b/Sources/PaystackUI/Components/PinTextView/UIKit/Core/PinTextField.swift index d035a2a..7c7d482 100644 --- a/Sources/PaystackUI/Components/PinTextView/UIKit/Core/PinTextField.swift +++ b/Sources/PaystackUI/Components/PinTextView/UIKit/Core/PinTextField.swift @@ -9,21 +9,21 @@ class PinTextField: UITextField { /// The default character placed in the text field slots var otpDefaultCharacter = "" /// The default background color of the text field slots before entering a character - var otpBackgroundColor: UIColor = UIColor(red: 0.949, green: 0.949, blue: 0.949, alpha: 1) + var otpBackgroundColor: UIColor = .pinSlotSurface /// The default background color of the text field slots after entering a character - var otpFilledBackgroundColor: UIColor = UIColor(red: 0.949, green: 0.949, blue: 0.949, alpha: 1) + var otpFilledBackgroundColor: UIColor = .pinSlotSurface /// The default corner raduis of the text field slots var otpCornerRaduis: CGFloat = 3 /// The default border color of the text field slots before entering a character - var otpDefaultBorderColor: UIColor = .gray + var otpDefaultBorderColor: UIColor = .pinSlotBorder /// The border color of the text field slots after entering a character - var otpFilledBorderColor: UIColor = .green + var otpFilledBorderColor: UIColor = .accentPrimary /// The default border width of the text field slots before entering a character var otpDefaultBorderWidth: CGFloat = 1 /// The border width of the text field slots after entering a character var otpFilledBorderWidth: CGFloat = 1 /// The default text color of the text - var otpTextColor: UIColor = .black + var otpTextColor: UIColor = .contentPrimary /// The default font size of the text var otpFontSize: CGFloat = 18 /// The default font of the text diff --git a/Sources/PaystackUI/Components/Styles/PrimaryButtonStyle.swift b/Sources/PaystackUI/Components/Styles/PrimaryButtonStyle.swift index eae42c0..be5eb41 100644 --- a/Sources/PaystackUI/Components/Styles/PrimaryButtonStyle.swift +++ b/Sources/PaystackUI/Components/Styles/PrimaryButtonStyle.swift @@ -36,19 +36,19 @@ struct PrimaryButtonStyle: ButtonStyle { extension PrimaryButtonStyle { var background: Color { - isEnabled ? .stackGreen : .stackGreen.opacity(0.6) + isEnabled ? .accentPrimary : .accentPrimaryDisabled } var pressedBackground: Color { - .stackGreen.opacity(0.75) + .accentPrimaryPressed } var foreground: Color { - .white + .contentOnAccent } var pressedForeground: Color { - .white.opacity(0.75) + .contentOnAccentPressed } } @@ -85,7 +85,7 @@ struct PaymentChannelSelectionButton: ButtonStyle { .padding() .frame(height: .buttonHeight) .frame(maxWidth: .infinity) - .focusedBorderColor(defaultColor: .gray01) + .focusedBorderColor(defaultColor: .borderSubtle) .font(.body16M) .background(configuration.isPressed ? pressedBackground : background) .foregroundColor(configuration.isPressed ? pressedForeground : foreground) @@ -99,19 +99,19 @@ struct PaymentChannelSelectionButton: ButtonStyle { extension PaymentChannelSelectionButton { var background: Color { - isEnabled ? .stackGreen : .stackGreen.opacity(0.6) + isEnabled ? .accentPrimary : .accentPrimaryDisabled } var pressedBackground: Color { - .stackGreen.opacity(0.75) + .accentPrimaryPressed } var foreground: Color { - .white + .contentOnAccent } var pressedForeground: Color { - .white.opacity(0.75) + .contentOnAccentPressed } } diff --git a/Sources/PaystackUI/Components/Styles/SecondaryButtonStyle.swift b/Sources/PaystackUI/Components/Styles/SecondaryButtonStyle.swift index 7e8b7de..747c392 100644 --- a/Sources/PaystackUI/Components/Styles/SecondaryButtonStyle.swift +++ b/Sources/PaystackUI/Components/Styles/SecondaryButtonStyle.swift @@ -38,19 +38,19 @@ struct SecondaryButtonStyle: ButtonStyle { extension SecondaryButtonStyle { var border: Color { - .green02 + .accentSecondary } var pressedBorder: Color { - .green02 + .accentSecondary } var foreground: Color { - .green02 + .accentSecondary } var pressedForeground: Color { - .green02 + .accentSecondary } } diff --git a/Sources/PaystackUI/Components/TestModeInidcator.swift b/Sources/PaystackUI/Components/TestModeInidcator.swift index 293db04..a52fb2d 100644 --- a/Sources/PaystackUI/Components/TestModeInidcator.swift +++ b/Sources/PaystackUI/Components/TestModeInidcator.swift @@ -4,14 +4,14 @@ struct TestModeInidcator: View { var body: some View { Text("TEST") - .foregroundColor(.warning01) + .foregroundColor(.testModeContent) .font(.smallTextM) .padding(.horizontal, .singlePadding + .halfPadding) .padding(.vertical, .halfPadding) - .background(Color.warning05) + .background(Color.testModeSurface) .overlay( RoundedRectangle(cornerRadius: 3) - .stroke(Color.warning04, lineWidth: 1) + .stroke(Color.testModeBorder, lineWidth: 1) ) } diff --git a/Sources/PaystackUI/Components/TimelineNode.swift b/Sources/PaystackUI/Components/TimelineNode.swift index fc1f552..bd2bd01 100644 --- a/Sources/PaystackUI/Components/TimelineNode.swift +++ b/Sources/PaystackUI/Components/TimelineNode.swift @@ -22,7 +22,7 @@ struct TimelineNode: View { VStack(spacing: .singlePadding) { ZStack { Circle() - .stroke(Color.stackGreen, lineWidth: 2) + .stroke(Color.accentPrimary, lineWidth: 2) .frame(width: diameter, height: diameter) .scaleEffect(rippleScale) .opacity(rippleOpacity) @@ -30,23 +30,23 @@ struct TimelineNode: View { switch state { case .complete: Circle() - .fill(Color.stackGreen) + .fill(Color.accentPrimary) .frame(width: diameter, height: diameter) .scaleEffect(nodeScale) Image(systemName: "checkmark") - .foregroundColor(.white) + .foregroundColor(.contentOnAccent) .font(.system(size: 14, weight: .bold)) .scaleEffect(checkmarkScale) case .pending: Circle() .stroke(style: StrokeStyle(lineWidth: 2, dash: [3, 3])) - .foregroundColor(.gray01) + .foregroundColor(.iconMuted) .frame(width: diameter, height: diameter) } } Text(label) .font(.body12M) - .foregroundColor(state == .complete ? .stackGreen : .navy03) + .foregroundColor(state == .complete ? .accentPrimary : .contentTertiary) .animation(.easeInOut(duration: 0.2), value: state) } .onChange(of: state) { newState in diff --git a/Sources/PaystackUI/Components/Views/SingleSelectionFormInputView.swift b/Sources/PaystackUI/Components/Views/SingleSelectionFormInputView.swift index bde301b..2d38973 100644 --- a/Sources/PaystackUI/Components/Views/SingleSelectionFormInputView.swift +++ b/Sources/PaystackUI/Components/Views/SingleSelectionFormInputView.swift @@ -40,7 +40,7 @@ struct SingleSelectionFormInputView Color { return Color(name, bundle: .current) } } + +#if canImport(UIKit) +import UIKit + +// MARK: - UIKit dynamic-colour bridge +// +// PIN entry uses UIKit primitives. Direct `UIColor(_: Color)` snapshots the +// SwiftUI colour against the current trait collection at init and does not +// track a later `.light` → `.dark` change. Using `UIColor(named:in:compatibleWith:)` +// with `compatibleWith: nil` resolves against the view's live trait +// collection, so appearance flips mid-session are honoured. +extension UIColor { + + static var pinSlotSurface: UIColor { + .paystackNamed("PinSlotSurface") + } + + static var pinSlotBorder: UIColor { + .paystackNamed("PinSlotBorder") + } + + static var accentPrimary: UIColor { + .paystackNamed("AccentPrimary") + } + + static var contentPrimary: UIColor { + .paystackNamed("ContentPrimary") + } + + fileprivate static func paystackNamed(_ name: String) -> UIColor { + return UIColor(named: name, in: .current, compatibleWith: nil) + ?? .clear + } +} +#endif diff --git a/Sources/PaystackUI/Design/DarkModePreviews.swift b/Sources/PaystackUI/Design/DarkModePreviews.swift new file mode 100644 index 0000000..ef65a42 --- /dev/null +++ b/Sources/PaystackUI/Design/DarkModePreviews.swift @@ -0,0 +1,222 @@ +import SwiftUI + +#if DEBUG + +@available(iOS 14.0, *) +struct DarkModeSwatchGrid: View { + + let title: String + let entries: [SwatchEntry] + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text(title) + .font(.body16M) + .foregroundColor(.contentPrimary) + + VStack(spacing: 6) { + ForEach(entries) { entry in + SwatchRow(entry: entry) + } + } + } + .padding(16) + } +} + +@available(iOS 14.0, *) +struct SwatchEntry: Identifiable { + let id: String + let color: Color + let notes: String? + + init(_ id: String, _ color: Color, notes: String? = nil) { + self.id = id + self.color = color + self.notes = notes + } +} + +@available(iOS 14.0, *) +private struct SwatchRow: View { + + let entry: SwatchEntry + + var body: some View { + HStack(spacing: 12) { + RoundedRectangle(cornerRadius: 6) + .fill(entry.color) + .frame(width: 40, height: 28) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Color.gray.opacity(0.3), lineWidth: 1)) + + VStack(alignment: .leading, spacing: 2) { + Text(entry.id) + .font(.body14M) + .foregroundColor(.contentPrimary) + if let notes = entry.notes { + Text(notes) + .font(.body12R) + .foregroundColor(.contentSecondary) + } + } + Spacer(minLength: 0) + } + } +} + +@available(iOS 14.0, *) +enum DarkModePalette { + + static let primary: [SwatchEntry] = [ + .init("stackBlue", .stackBlue, notes: "#011B33 — content primary today"), + .init("navy02", .navy02, notes: "#596A7A — content secondary today"), + .init("navy03", .navy03, notes: "#98A6AD — content tertiary today"), + .init("navy04", .navy04, notes: "#CCD1D6 — border strong / placeholder"), + .init("navy05", .navy05, notes: "#E6E8EB — border subtle / surface secondary") + ] + + static let secondary: [SwatchEntry] = [ + .init("stackGreen", .stackGreen, notes: "#3BB75E — accent primary"), + .init("green02", .green02, notes: "#1D853A — accent secondary (fails 4.5:1 dark)") + ] + + static let error: [SwatchEntry] = [ + .init("error01", .error01, notes: "#880202 — content error (fails dark)"), + .init("error02", .error02, notes: "#D44141 — border error") + ] + + static let warning: [SwatchEntry] = [ + .init("warning01", .warning01, notes: "#673700 — test-mode content"), + .init("warning02", .warning02, notes: "#FFAA22 — content warning / countdown"), + .init("warning04", .warning04, notes: "#FFE4B8 — test-mode border"), + .init("warning05", .warning05, notes: "#FFF6DF — test-mode surface") + ] + + static let neutral: [SwatchEntry] = [ + .init("gray01", .gray01, notes: "#999DA1 — muted icon / track / shimmer base") + ] + + static let semanticSurfaces: [SwatchEntry] = [ + .init("surfacePrimary", .surfacePrimary, notes: "Sheet root"), + .init("surfaceSecondary", .surfaceSecondary, notes: "Filled inset chip"), + .init("surfaceTertiary", .surfaceTertiary, notes: "Deepest surface (reserved)"), + .init("surfaceInsetTranslucent", .surfaceInsetTranslucent, notes: "Capitec stepsCard"), + .init("overlayScrim", .overlayScrim, notes: "Reserved — no current site") + ] + + static let semanticBorders: [SwatchEntry] = [ + .init("borderPrimary", .borderPrimary, notes: "Hairline on cards"), + .init("borderSubtle", .borderSubtle, notes: "Channel row"), + .init("borderStrong", .borderStrong, notes: "Input / picker"), + .init("borderError", .borderError, notes: "Field error border") + ] + + static let semanticContent: [SwatchEntry] = [ + .init("contentPrimary", .contentPrimary, notes: "Body / heading text"), + .init("contentSecondary", .contentSecondary, notes: "Secondary body"), + .init("contentTertiary", .contentTertiary, notes: "Caption"), + .init("contentPlaceholder", .contentPlaceholder, notes: "Placeholder"), + .init("contentWarning", .contentWarning, notes: "Warning / countdown"), + .init("contentError", .contentError, notes: "Validation message"), + .init("contentOnAccent", .contentOnAccent, notes: "Label on green CTA"), + .init("contentOnAccentPressed", .contentOnAccentPressed, notes: "Pressed label") + ] + + static let semanticAccents: [SwatchEntry] = [ + .init("accentPrimary", .accentPrimary, notes: "Green CTA / focus ring"), + .init("accentPrimaryDisabled", .accentPrimaryDisabled, notes: "CTA disabled"), + .init("accentPrimaryPressed", .accentPrimaryPressed, notes: "CTA pressed"), + .init("accentSecondary", .accentSecondary, notes: "Outline button text") + ] + + static let semanticAuxiliaries: [SwatchEntry] = [ + .init("iconMuted", .iconMuted, notes: "Muted glyph"), + .init("spinnerTint", .spinnerTint, notes: "Spinner"), + .init("trackInactive", .trackInactive, notes: "Progress track") + ] + + static let semanticWarningInfoTest: [SwatchEntry] = [ + .init("warningSurface", .warningSurface, notes: "Warning banner"), + .init("infoSurface", .infoSurface, notes: "Info banner"), + .init("toastSurface", .toastSurface, notes: "Toast pill"), + .init("testModeContent", .testModeContent, notes: "Test-mode chip text"), + .init("testModeSurface", .testModeSurface, notes: "Test-mode chip fill"), + .init("testModeBorder", .testModeBorder, notes: "Test-mode chip border") + ] + + static let semanticFixed: [SwatchEntry] = [ + .init("qrPlate", .qrPlate, notes: "QR quiet-zone — must not invert"), + .init("shimmerBase", .shimmerBase, notes: "QR loader base"), + .init("shimmerHighlight", .shimmerHighlight, notes: "QR loader gloss"), + .init("pinSlotSurface", .pinSlotSurface, notes: "PIN slot fill"), + .init("pinSlotBorder", .pinSlotBorder, notes: "PIN slot border") + ] +} + +@available(iOS 14.0, *) +struct DarkModePreviews_Previews: PreviewProvider { + + static var previews: some View { + Group { + allSwatches + .previewDisplayName("All tokens — Light") + .preferredColorScheme(.light) + + allSwatches + .background(Color.surfacePrimary) + .previewDisplayName("All tokens — Dark") + .preferredColorScheme(.dark) + } + } + + private static var allSwatches: some View { + ScrollView { + VStack(alignment: .leading, spacing: 24) { + + Text("Semantic tokens (target layer)") + .font(.heading2) + .foregroundColor(.contentPrimary) + .padding(.horizontal, 16) + + DarkModeSwatchGrid(title: "Surfaces", + entries: DarkModePalette.semanticSurfaces) + DarkModeSwatchGrid(title: "Borders", + entries: DarkModePalette.semanticBorders) + DarkModeSwatchGrid(title: "Content", + entries: DarkModePalette.semanticContent) + DarkModeSwatchGrid(title: "Accents", + entries: DarkModePalette.semanticAccents) + DarkModeSwatchGrid(title: "Auxiliaries", + entries: DarkModePalette.semanticAuxiliaries) + DarkModeSwatchGrid(title: "Warning / info / test-mode / toast", + entries: DarkModePalette.semanticWarningInfoTest) + DarkModeSwatchGrid(title: "Fixed / shimmer / PIN", + entries: DarkModePalette.semanticFixed) + + Divider() + .padding(.horizontal, 16) + + Text("Hue tokens (kept as internal primitives per Q8)") + .font(.heading2) + .foregroundColor(.contentPrimary) + .padding(.horizontal, 16) + + DarkModeSwatchGrid(title: "Primary", + entries: DarkModePalette.primary) + DarkModeSwatchGrid(title: "Secondary", + entries: DarkModePalette.secondary) + DarkModeSwatchGrid(title: "Error", + entries: DarkModePalette.error) + DarkModeSwatchGrid(title: "Warning", + entries: DarkModePalette.warning) + DarkModeSwatchGrid(title: "Neutral", + entries: DarkModePalette.neutral) + } + .padding(.vertical, 16) + } + } +} + +#endif diff --git a/Sources/PaystackUI/Images/Images.swift b/Sources/PaystackUI/Images/Images.swift index 7cdf492..30b3863 100644 --- a/Sources/PaystackUI/Images/Images.swift +++ b/Sources/PaystackUI/Images/Images.swift @@ -94,13 +94,13 @@ extension Image { static var dropDownIndicator: some View { Image(systemName: "arrowtriangle.down.circle.fill") - .foregroundColor(.gray01) + .foregroundColor(.iconMuted) } static var errorIcon: some View { Image(systemName: "exclamationmark.triangle.fill") .resizable() - .foregroundColor(.warning02) + .foregroundColor(.contentWarning) .aspectRatio(contentMode: .fit) .frame(width: .quadPadding) } @@ -108,7 +108,7 @@ extension Image { static var radioButtonUnselected: some View { Image(systemName: "circle") .resizable() - .foregroundColor(.black) + .foregroundColor(.contentTertiary) .aspectRatio(contentMode: .fit) .frame(width: .doublePadding) } @@ -116,7 +116,7 @@ extension Image { static var radioButtonSelected: some View { Image(systemName: "checkmark.circle.fill") .resizable() - .foregroundColor(.stackGreen) + .foregroundColor(.accentPrimary) .aspectRatio(contentMode: .fit) .frame(width: .doublePadding) }