Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions AppUIComponents/Common/ThemeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,25 @@ import Combine
@MainActor
final class ThemeManager: ObservableObject {

// MARK: - Persistence
private static let persistenceKey = "auth0_selected_theme"

// MARK: - Published State
@Published private(set) var currentTheme: Auth0Theme = Auth0Theme()
@Published private(set) var activeOption: ThemeOption = .automatic
@Published private(set) var currentTheme: Auth0Theme
@Published private(set) var activeOption: ThemeOption

// MARK: - Init
init() {
let saved = UserDefaults.standard.string(forKey: Self.persistenceKey)
.flatMap(ThemeOption.init(rawValue:)) ?? .automatic
activeOption = saved
currentTheme = saved.theme
}

// MARK: - Apply
func apply(_ option: ThemeOption) {
activeOption = option
currentTheme = option.theme
UserDefaults.standard.set(option.rawValue, forKey: Self.persistenceKey)
}
}
35 changes: 20 additions & 15 deletions AppUIComponents/LoginOptions/LoginOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

VStack(spacing: 0) {
Image("ic_auth0", bundle: .main)
.renderingMode(.template)
.foregroundStyle(theme.colors.text.bold)
.aspectRatio(106/40, contentMode: .fit)
.frame(width: 106)
.padding(.top, 60)
Expand All @@ -42,6 +44,8 @@
} label: {
HStack(spacing: 6) {
Image("ic_appearance")
.renderingMode(.template)
.foregroundStyle(theme.colors.background.primary)

Text("Appearance")
.auth0TextStyle(theme.typography.title)
Expand Down Expand Up @@ -80,7 +84,7 @@
} message: {
Text("Something went wrong!")
}
.onChange(of: viewModel.error) { _ in

Check warning on line 87 in AppUIComponents/LoginOptions/LoginOptionsView.swift

View workflow job for this annotation

GitHub Actions / Test on visionOS using Xcode 26.2

'onChange(of:perform:)' was deprecated in visionOS 1.0: Use `onChange` with a two or zero parameter action closure instead.
if viewModel.error.isNotNil {
showAlert = true
}
Expand Down Expand Up @@ -109,7 +113,7 @@
.padding(.horizontal, 36)
.padding(.vertical, 28)
.background(theme.colors.background.layerTop)
.clipShape(RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: theme.radius.large))
.shadow(color: Color.black.opacity(0.18), radius: 24, x: 0, y: 8)
}
}
Expand All @@ -118,24 +122,24 @@
private var backgroundView: some View {
GeometryReader { geo in
ZStack {
Color("F5F5F5", bundle: .main)
theme.colors.background.layerBase

VStack(spacing: 0) {
Spacer()

LinearGradient(
stops: [
Gradient.Stop(color: Color(red: 0.97, green: 0.97, blue: 0.96), location: 0.13),
Gradient.Stop(color: Color(red: 0.83, green: 0.82, blue: 1), location: 0.28),
Gradient.Stop(color: Color(red: 0.97, green: 0.73, blue: 0.47), location: 0.52),
Gradient.Stop(color: Color(red: 0.94, green: 0.61, blue: 0.02), location: 0.71),
Gradient.Stop(color: Color(red: 0.93, green: 0.92, blue: 0.91), location: 0.99),
],
startPoint: UnitPoint(x: 0.24, y: 0.22),
endPoint: UnitPoint(x: 0.95, y: 0.24)
stops: [
Gradient.Stop(color: theme.colors.background.layerBase, location: 0.00),
Gradient.Stop(color: theme.colors.background.accent.opacity(0.35), location: 0.25),
Gradient.Stop(color: theme.colors.background.primary.opacity(0.25), location: 0.52),
Gradient.Stop(color: theme.colors.background.accent.opacity(0.20), location: 0.75),
Gradient.Stop(color: theme.colors.background.layerBase, location: 1.00),
],
startPoint: UnitPoint(x: 0.24, y: 0.22),
endPoint: UnitPoint(x: 0.95, y: 0.24)
)
.frame(height: geo.size.height/4)
.blur(radius: 155.91499)
.frame(height: geo.size.height / 4)
.blur(radius: 155)
}
}
.ignoresSafeArea()
Expand All @@ -148,6 +152,7 @@
VStack(spacing: 16) {
Text("Choose how to sign in")
.auth0TextStyle(theme.typography.display)
.foregroundStyle(theme.colors.text.bold)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.bottom, theme.spacing.xs)
Expand Down
14 changes: 11 additions & 3 deletions AppUIComponents/Splash/SplashView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@ import SwiftUI
import Auth0UniversalComponents

struct SplashView: View {

// MARK: - Router
@EnvironmentObject var router: Router<SampleAppRoute>


// MARK: - Theme
@Environment(\.auth0Theme) private var theme

// MARK: - Main body
var body: some View {
GeometryReader { proxy in
Image("ic_auth0", bundle: .main)
.renderingMode(.template)
.foregroundStyle(theme.colors.text.bold)
.position(x: proxy.frame(in: .local).midX, y: proxy.frame(in: .local).midY)
}.onAppear {
}
.background(theme.colors.background.layerBase)
.ignoresSafeArea()
.onAppear {
withAnimation(.easeInOut.delay(2)) {
navigateToLoginOptions()
}
Expand Down
12 changes: 7 additions & 5 deletions AppUIComponents/Welcome/WelcomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct WelcomeView: View {
#if !os(macOS)
.navigationBarBackButtonHidden()
#endif
.background(theme.colors.background.layerBase)
.background(theme.colors.background.layerBase.ignoresSafeArea())
}

// MARK: - Header View
Expand Down Expand Up @@ -81,6 +81,8 @@ struct WelcomeView: View {

VStack(alignment: .leading, spacing: 0) {
Image(item.icon.wrappedValue, bundle: .main)
.renderingMode(.template)
.foregroundStyle(theme.colors.background.primary)
.padding(theme.spacing.xxs)
.frame(width: theme.sizes.iconLarge, height: theme.sizes.iconLarge)

Expand All @@ -101,9 +103,9 @@ struct WelcomeView: View {
.frame(maxWidth: .infinity, minHeight: tileHeight > 0 ? tileHeight : nil)
.contentShape(Rectangle())
.background(theme.colors.background.layerMedium)
.cornerRadius(20)
.cornerRadius(theme.radius.large)
.overlay {
RoundedRectangle(cornerRadius: 20)
RoundedRectangle(cornerRadius: theme.radius.large)
.inset(by: 0.5)
.stroke(theme.colors.border.regular, lineWidth: 1)
}
Expand All @@ -129,8 +131,8 @@ struct WelcomeView: View {
}
} label: {
Text("Log out")
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(Color("262420", bundle: .main))
.auth0TextStyle(theme.typography.label)
.foregroundStyle(theme.colors.text.bold)
.frame(maxWidth: .infinity, alignment: .center)
.frame(height: theme.sizes.buttonHeight)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct EnrollPasskeyView: View {
endPoint: UnitPoint(x: 0.5, y: 1)
)
)
.background(Color.white)
.background(theme.colors.background.layerTop)
.clipShape(RoundedRectangle(cornerRadius: theme.radius.button))
.overlay(
RoundedRectangle(cornerRadius: theme.radius.button)
Expand All @@ -101,7 +101,7 @@ struct EnrollPasskeyView: View {

}
.padding(.all, theme.spacing.lg)
.background(Color("Muted", bundle: ResourceBundle.default))
.background(theme.colors.background.layerMedium)
.clipShape(RoundedRectangle(cornerRadius: theme.radius.medium))
.fullScreenCoverOrSheet(isPresented: $viewModel.showLoader) {
Auth0Loader()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ struct MyAccountAuthMethodView: View {
var body: some View {
HStack {
Image(viewModel.image(), bundle: ResourceBundle.default)
.renderingMode(.template)
.foregroundStyle(theme.colors.background.primary)
.frame(width: theme.sizes.iconMedium, height: theme.sizes.iconMedium)
.padding(.trailing, theme.spacing.md)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ public struct MyAccountAuthMethodsView: View {
// Embedded: no inner NavigationStack β€” destinations attach to the
// host stack. The SDK router is redirected to the host path on appear.
sdkRootContent
.navigationTitle(Text("Login & Security"))
.themedNavigationTitle("Login & Security", theme: theme)
#if !os(macOS)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(theme.colors.background.layerBase, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
#endif
#if !os(macOS)
.navigationBarBackButtonHidden(true)
Expand All @@ -109,9 +111,11 @@ public struct MyAccountAuthMethodsView: View {
// Standalone: the SDK owns the full NavigationStack.
NavigationStack(path: $router.path) {
sdkRootContent
.navigationTitle(Text("Login & Security"))
.themedNavigationTitle("Login & Security", theme: theme)
#if !os(macOS)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(theme.colors.background.layerBase, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
#endif
.navigationDestination(for: Route.self) { route in
ViewFactory.view(for: route, delegate: viewModel)
Expand All @@ -127,6 +131,8 @@ public struct MyAccountAuthMethodsView: View {
@ViewBuilder
private var sdkRootContent: some View {
ZStack {
theme.colors.background.layerBase
.ignoresSafeArea()
if let errorViewModel = viewModel.errorViewModel {
ErrorScreen(viewModel: errorViewModel)
} else {
Expand Down Expand Up @@ -170,7 +176,7 @@ public struct MyAccountAuthMethodsView: View {
.padding(.horizontal, 36)
.padding(.vertical, 28)
.background(theme.colors.background.layerTop)
.clipShape(RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: theme.radius.large))
.shadow(color: Color.black.opacity(0.18), radius: 24, x: 0, y: 8)
}
}
Expand Down
7 changes: 7 additions & 0 deletions Auth0UniversalComponents/Core/Theme/Auth0RadiusTokens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public protocol Auth0RadiusTokens: Sendable {
/// 16 pt β€” Primary CTA buttons and authenticator-method cards.
var button: CGFloat { get }

/// 20 pt β€” Floating dialog / modal cards (loading overlays, alert cards).
var large: CGFloat { get }

/// 24 pt β€” Fully rounded pill-style outline buttons ("Copy as Code", "Copy Code").
var pill: CGFloat { get }
}
Expand All @@ -45,6 +48,7 @@ public struct DefaultAuth0RadiusTokens: Auth0RadiusTokens {
public var medium: CGFloat
public var inputField: CGFloat
public var button: CGFloat
public var large: CGFloat
public var pill: CGFloat

/// Creates the default Auth0 corner-radius scale with optional per-token overrides.
Expand All @@ -54,18 +58,21 @@ public struct DefaultAuth0RadiusTokens: Auth0RadiusTokens {
/// - medium: Default `12` β€” used for banner cards.
/// - inputField: Default `14` β€” used for text inputs and code containers.
/// - button: Default `16` β€” used for CTA buttons and auth-method cards.
/// - large: Default `20` β€” used for floating dialog / modal cards.
/// - pill: Default `24` β€” used for pill-shaped outline buttons.
public init(
small: CGFloat = 8,
medium: CGFloat = 12,
inputField: CGFloat = 14,
button: CGFloat = 16,
large: CGFloat = 20,
pill: CGFloat = 24
) {
self.small = small
self.medium = medium
self.inputField = inputField
self.button = button
self.large = large
self.pill = pill
}
}
2 changes: 1 addition & 1 deletion Auth0UniversalComponents/Core/Utils/Auth0Loader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct Auth0Loader: View {
.padding(.horizontal, 36)
.padding(.vertical, 28)
.background(theme.colors.background.layerTop)
.clipShape(RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: theme.radius.large))
.shadow(color: Color.black.opacity(0.18), radius: 24, x: 0, y: 8)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ struct ErrorScreen: View {
.padding(theme.spacing.md)
}
}
.background(theme.colors.background.layerBase)
.background(theme.colors.background.layerBase.ignoresSafeArea())
}
}
7 changes: 6 additions & 1 deletion Auth0UniversalComponents/Core/Utils/OTPTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ struct OTPTextField: UIViewRepresentable {
var index: Int
/// Total number of digits in the OTP code
var digitCount: Int
/// Cursor (caret) tint colour β€” should match the active theme's primary colour
var cursorTintColor: Color
/// Callback when text is entered in this field
var setText: ((String) -> Void)
/// Callback when the enter/return key is pressed
Expand All @@ -45,7 +47,7 @@ struct OTPTextField: UIViewRepresentable {
textField.clearButtonMode = .never
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.tintColor = .gray
textField.tintColor = UIColor(cursorTintColor)
textField.keyboardType = .numberPad
textField.textContentType = .oneTimeCode
let toolbar = UIToolbar()
Expand All @@ -64,6 +66,7 @@ struct OTPTextField: UIViewRepresentable {
/// Updates the UITextField when the SwiftUI binding changes.
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = self.getText()
uiView.tintColor = UIColor(cursorTintColor)
self.setSelection(uiView)

uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
Expand Down Expand Up @@ -167,6 +170,8 @@ struct OTPTextField: NSViewRepresentable {
var index: Int
/// Total number of digits in the OTP code
var digitCount: Int
/// Cursor tint colour β€” accepted for API parity with the iOS version; AppKit has no tintColor equivalent
var cursorTintColor: Color
/// Callback when text is entered in this field
var setText: ((String) -> Void)
/// Callback when the enter/return key is pressed
Expand Down
26 changes: 26 additions & 0 deletions Auth0UniversalComponents/Core/Utils/View+Extension.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
import SwiftUI

extension View {
/// Applies a navigation title whose text colour adapts to the active Auth0 theme.
///
/// SwiftUI's built-in `.navigationTitle` text colour is system-controlled and
/// cannot be overridden via foreground modifiers. This helper keeps
/// `.navigationTitle` so that child screens still show the correct back-button
/// label, and additionally injects a `ToolbarItem(placement: .principal)` to
/// render the title in `theme.colors.text.bold` using the theme typography.
///
/// - Parameters:
/// - title: The navigation title string.
/// - theme: The active `Auth0Theme` to source the colour and typography from.
@ViewBuilder
func themedNavigationTitle(_ title: String, theme: Auth0Theme) -> some View {
self
.navigationTitle(title)
#if !os(macOS)
.toolbar {
ToolbarItem(placement: .principal) {
Text(title)
.auth0TextStyle(theme.typography.title)
.foregroundStyle(theme.colors.text.bold)
}
}
#endif
}

/// Attaches a toast notification modifier to the view.
///
/// This convenience method applies the ToastModifier to display toast notifications
Expand Down
Loading
Loading