From c04772901a3921983f561a3dc1bf6624beb1eff7 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 18 Sep 2025 17:22:11 +0530 Subject: [PATCH 1/9] chore(react): refactor sign-In and sign-up components - Updated EmailOtp, IdentifierFirst, MultiOptionButton, SmsOtp, Totp, and UsernamePassword components to ensure consistent button properties (fullWidth, color, variant). - Removed redundant button properties from the components. - Refactored SignInOptionFactory to improve the handling of authenticator components, ensuring proper passing of props. - Deleted unused GoogleButton component from SignUp options and restructured imports. - Added new social button components for Facebook, GitHub, Google, LinkedIn, Microsoft, and SignInWithEthereum. - Implemented a new SocialButton component for handling social logins with external identity providers. --- .../presentation/SignIn/options/EmailOtp.tsx | 6 +- .../SignIn/options/IdentifierFirst.tsx | 6 +- .../SignIn/options/MultiOptionButton.tsx | 6 +- .../SignIn/options/SignInOptionFactory.tsx | 130 ++++++++++++++---- .../presentation/SignIn/options/SmsOtp.tsx | 6 +- .../presentation/SignIn/options/Totp.tsx | 6 +- .../SignIn/options/UsernamePassword.tsx | 6 +- .../SignUp/options/GoogleButton.tsx | 89 ------------ .../SignUp/options/SignUpOptionFactory.tsx | 91 +++++++++--- .../presentation/SignUp/options/index.ts | 33 ----- .../{SignIn => }/options/FacebookButton.tsx | 31 ++--- .../{SignIn => }/options/GitHubButton.tsx | 30 ++-- .../{SignIn => }/options/GoogleButton.tsx | 30 ++-- .../{SignIn => }/options/LinkedInButton.tsx | 32 ++--- .../{SignIn => }/options/MicrosoftButton.tsx | 32 ++--- .../options/SignInWithEthereumButton.tsx | 31 ++--- .../{SignIn => }/options/SocialButton.tsx | 40 ++---- 17 files changed, 274 insertions(+), 331 deletions(-) delete mode 100644 packages/react/src/components/presentation/SignUp/options/GoogleButton.tsx delete mode 100644 packages/react/src/components/presentation/SignUp/options/index.ts rename packages/react/src/components/presentation/{SignIn => }/options/FacebookButton.tsx (74%) rename packages/react/src/components/presentation/{SignIn => }/options/GitHubButton.tsx (80%) rename packages/react/src/components/presentation/{SignIn => }/options/GoogleButton.tsx (81%) rename packages/react/src/components/presentation/{SignIn => }/options/LinkedInButton.tsx (75%) rename packages/react/src/components/presentation/{SignIn => }/options/MicrosoftButton.tsx (71%) rename packages/react/src/components/presentation/{SignIn => }/options/SignInWithEthereumButton.tsx (71%) rename packages/react/src/components/presentation/{SignIn => }/options/SocialButton.tsx (65%) diff --git a/packages/react/src/components/presentation/SignIn/options/EmailOtp.tsx b/packages/react/src/components/presentation/SignIn/options/EmailOtp.tsx index 39616690..f854e897 100644 --- a/packages/react/src/components/presentation/SignIn/options/EmailOtp.tsx +++ b/packages/react/src/components/presentation/SignIn/options/EmailOtp.tsx @@ -95,13 +95,13 @@ const EmailOtp: FC = ({ })} - ); -}; - -export default GoogleButton; diff --git a/packages/react/src/components/presentation/SignUp/options/SignUpOptionFactory.tsx b/packages/react/src/components/presentation/SignUp/options/SignUpOptionFactory.tsx index f22c84b1..c27c795e 100644 --- a/packages/react/src/components/presentation/SignUp/options/SignUpOptionFactory.tsx +++ b/packages/react/src/components/presentation/SignUp/options/SignUpOptionFactory.tsx @@ -23,7 +23,6 @@ import DateInput from './DateInput'; import DividerComponent from './DividerComponent'; import EmailInput from './EmailInput'; import FormContainer from './FormContainer'; -import GoogleButton from './GoogleButton'; import ImageComponent from './ImageComponent'; import NumberInput from './NumberInput'; import PasswordInput from './PasswordInput'; @@ -31,6 +30,12 @@ import ButtonComponent from './SubmitButton'; import TelephoneInput from './TelephoneInput'; import TextInput from './TextInput'; import Typography from './Typography'; +import GoogleButton from '../../options/GoogleButton'; +import GitHubButton from '../../options/GitHubButton'; +import MicrosoftButton from '../../options/MicrosoftButton'; +import LinkedInButton from '../../options/LinkedInButton'; +import FacebookButton from '../../options/FacebookButton'; +import SignInWithEthereumButton from '../../options/SignInWithEthereumButton'; /** * Base props that all sign-up option components share. @@ -102,12 +107,10 @@ export interface BaseSignUpOptionProps extends WithPreferences { /** * Creates the appropriate sign-up component based on the component type. */ -export const createSignUpComponent = (props: BaseSignUpOptionProps): ReactElement => { - const {component} = props; - +export const createSignUpComponent = ({component, onSubmit, ...rest}: BaseSignUpOptionProps): ReactElement => { switch (component.type) { case EmbeddedFlowComponentType.Typography: - return ; + return ; case EmbeddedFlowComponentType.Input: // Determine input type based on variant or config @@ -115,47 +118,99 @@ export const createSignUpComponent = (props: BaseSignUpOptionProps): ReactElemen const inputType = component.config['type']?.toLowerCase(); if (inputVariant === 'EMAIL' || inputType === 'email') { - return ; + return ; } + if (inputVariant === 'PASSWORD' || inputType === 'password') { - return ; + return ; } + if (inputVariant === 'TELEPHONE' || inputType === 'tel') { - return ; + return ; } + if (inputVariant === 'NUMBER' || inputType === 'number') { - return ; + return ; } + if (inputVariant === 'DATE' || inputType === 'date') { - return ; + return ; } + if (inputVariant === 'CHECKBOX' || inputType === 'checkbox') { - return ; + return ; } - return ; + + return ; case EmbeddedFlowComponentType.Button: { const buttonVariant: string | undefined = component.variant?.toUpperCase(); const buttonText: string = component.config['text'] || component.config['label'] || ''; // TODO: The connection type should come as metadata. - if (buttonVariant === 'SOCIAL' && buttonText.toLowerCase().includes('google')) { - return ; + if (buttonVariant === 'SOCIAL') { + if (buttonText.toLowerCase().includes('google')) { + return ( + onSubmit(component, {})} {...rest}> + {buttonText} + + ); + } + + if (buttonText.toLowerCase().includes('github')) { + return ( + onSubmit(component, {})} {...rest}> + {buttonText} + + ); + } + + if (buttonText.toLowerCase().includes('microsoft')) { + return ( + onSubmit(component, {})} {...rest}> + {buttonText} + + ); + } + + if (buttonText.toLowerCase().includes('facebook')) { + return ( + onSubmit(component, {})} {...rest}> + {buttonText} + + ); + } + + if (buttonText.toLowerCase().includes('linkedin')) { + return ( + onSubmit(component, {})} {...rest}> + {buttonText} + + ); + } + + if (buttonText.toLowerCase().includes('ethereum')) { + return ( + onSubmit(component, {})} {...rest}> + {buttonText} + + ); + } } // Use the generic ButtonComponent for all other button variants // It will handle PRIMARY, SECONDARY, TEXT, SOCIAL mappings internally - return ; + return ; } case EmbeddedFlowComponentType.Form: - return ; + return ; case EmbeddedFlowComponentType.Divider: - return ; + return ; case EmbeddedFlowComponentType.Image: - return ; + return ; default: return
; diff --git a/packages/react/src/components/presentation/SignUp/options/index.ts b/packages/react/src/components/presentation/SignUp/options/index.ts deleted file mode 100644 index 361077b8..00000000 --- a/packages/react/src/components/presentation/SignUp/options/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you 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. - */ - -export {default as TextInput} from './TextInput'; -export {default as EmailInput} from './EmailInput'; -export {default as PasswordInput} from './PasswordInput'; -export {default as SubmitButton} from './SubmitButton'; -export {default as SocialButton} from './SocialButton'; -export {default as GoogleButton} from './GoogleButton'; -export {default as Typography} from './Typography'; -export {default as FormContainer} from './FormContainer'; - -export { - createSignUpComponent, - createSignUpOptionFromComponent, - renderSignUpComponents, - type BaseSignUpOptionProps, -} from './SignUpOptionFactory'; diff --git a/packages/react/src/components/presentation/SignIn/options/FacebookButton.tsx b/packages/react/src/components/presentation/options/FacebookButton.tsx similarity index 74% rename from packages/react/src/components/presentation/SignIn/options/FacebookButton.tsx rename to packages/react/src/components/presentation/options/FacebookButton.tsx index 3bfd5f40..f6742d1d 100644 --- a/packages/react/src/components/presentation/SignIn/options/FacebookButton.tsx +++ b/packages/react/src/components/presentation/options/FacebookButton.tsx @@ -16,40 +16,31 @@ * under the License. */ -import {FC} from 'react'; -import Button from '../../../primitives/Button/Button'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; -import useTranslation from '../../../../hooks/useTranslation'; +import {FC, HTMLAttributes} from 'react'; +import Button from '../../primitives/Button/Button'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; +import useTranslation from '../../../hooks/useTranslation'; /** * Facebook Sign-In Button Component. * Handles authentication with Facebook identity provider. */ -const FacebookButton: FC = ({ - authenticator, +const FacebookButton: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', - submitButtonText, preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; return ( ); }; diff --git a/packages/react/src/components/presentation/SignIn/options/GitHubButton.tsx b/packages/react/src/components/presentation/options/GitHubButton.tsx similarity index 80% rename from packages/react/src/components/presentation/SignIn/options/GitHubButton.tsx rename to packages/react/src/components/presentation/options/GitHubButton.tsx index a59e05c3..ba200da2 100644 --- a/packages/react/src/components/presentation/SignIn/options/GitHubButton.tsx +++ b/packages/react/src/components/presentation/options/GitHubButton.tsx @@ -16,39 +16,31 @@ * under the License. */ -import {FC} from 'react'; -import Button from '../../../primitives/Button/Button'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; -import useTranslation from '../../../../hooks/useTranslation'; +import {FC, HTMLAttributes} from 'react'; +import Button from '../../primitives/Button/Button'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; +import useTranslation from '../../../hooks/useTranslation'; /** * GitHub Sign-In Button Component. * Handles authentication with GitHub identity provider. */ -const GitHubButton: FC = ({ - authenticator, +const GitHubButton: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; return ( ); }; diff --git a/packages/react/src/components/presentation/SignIn/options/GoogleButton.tsx b/packages/react/src/components/presentation/options/GoogleButton.tsx similarity index 81% rename from packages/react/src/components/presentation/SignIn/options/GoogleButton.tsx rename to packages/react/src/components/presentation/options/GoogleButton.tsx index ca4f60d0..7ad736e4 100644 --- a/packages/react/src/components/presentation/SignIn/options/GoogleButton.tsx +++ b/packages/react/src/components/presentation/options/GoogleButton.tsx @@ -16,39 +16,31 @@ * under the License. */ -import {FC} from 'react'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; -import useTranslation from '../../../../hooks/useTranslation'; -import Button from '../../../primitives/Button/Button'; +import {FC, HTMLAttributes} from 'react'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; +import useTranslation from '../../../hooks/useTranslation'; +import Button from '../../primitives/Button/Button'; /** * Google Sign-In Button Component. * Handles authentication with Google identity provider. */ -const GoogleButton: FC = ({ - authenticator, +const GoogleButton: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; return ( ); }; diff --git a/packages/react/src/components/presentation/SignIn/options/LinkedInButton.tsx b/packages/react/src/components/presentation/options/LinkedInButton.tsx similarity index 75% rename from packages/react/src/components/presentation/SignIn/options/LinkedInButton.tsx rename to packages/react/src/components/presentation/options/LinkedInButton.tsx index 24ae3c10..1eb0a90b 100644 --- a/packages/react/src/components/presentation/SignIn/options/LinkedInButton.tsx +++ b/packages/react/src/components/presentation/options/LinkedInButton.tsx @@ -16,39 +16,31 @@ * under the License. */ -import {FC} from 'react'; -import Button from '../../../primitives/Button/Button'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; -import useTranslation from '../../../../hooks/useTranslation'; +import {FC, HTMLAttributes} from 'react'; +import Button from '../../primitives/Button/Button'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; +import useTranslation from '../../../hooks/useTranslation'; /** * LinkedIn Sign-In Button Component. * Handles authentication with LinkedIn identity provider. */ -const LinkedInButton: FC = ({ - authenticator, +const LinkedInButton: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; return ( ); }; diff --git a/packages/react/src/components/presentation/SignIn/options/MicrosoftButton.tsx b/packages/react/src/components/presentation/options/MicrosoftButton.tsx similarity index 71% rename from packages/react/src/components/presentation/SignIn/options/MicrosoftButton.tsx rename to packages/react/src/components/presentation/options/MicrosoftButton.tsx index ac3f6b6c..4e2cf69f 100644 --- a/packages/react/src/components/presentation/SignIn/options/MicrosoftButton.tsx +++ b/packages/react/src/components/presentation/options/MicrosoftButton.tsx @@ -16,39 +16,30 @@ * under the License. */ -import {FC} from 'react'; -import Button from '../../../primitives/Button/Button'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; -import useTranslation from '../../../../hooks/useTranslation'; +import {FC, HTMLAttributes} from 'react'; +import Button from '../../primitives/Button/Button'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; +import useTranslation from '../../../hooks/useTranslation'; /** * Microsoft Sign-In Button Component. * Handles authentication with Microsoft identity provider. */ -const MicrosoftButton: FC = ({ - authenticator, +const MicrosoftButton: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; return ( ); }; diff --git a/packages/react/src/components/presentation/SignIn/options/SignInWithEthereumButton.tsx b/packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx similarity index 71% rename from packages/react/src/components/presentation/SignIn/options/SignInWithEthereumButton.tsx rename to packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx index d39a7b12..0174f431 100644 --- a/packages/react/src/components/presentation/SignIn/options/SignInWithEthereumButton.tsx +++ b/packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx @@ -16,40 +16,30 @@ * under the License. */ -import {FC} from 'react'; -import Button from '../../../primitives/Button/Button'; -import useTranslation from '../../../../hooks/useTranslation'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; +import {FC, HTMLAttributes} from 'react'; +import Button from '../../primitives/Button/Button'; +import useTranslation from '../../../hooks/useTranslation'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; /** * Sign In With Ethereum Button Component. * Handles authentication with Sign In With Ethereum identity provider. */ -const SignInWithEthereumButton: FC = ({ - authenticator, +const SignInWithEthereumButton: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; - return ( ); }; diff --git a/packages/react/src/components/presentation/SignIn/options/SocialButton.tsx b/packages/react/src/components/presentation/options/SocialButton.tsx similarity index 65% rename from packages/react/src/components/presentation/SignIn/options/SocialButton.tsx rename to packages/react/src/components/presentation/options/SocialButton.tsx index db4e80c9..6516015e 100644 --- a/packages/react/src/components/presentation/SignIn/options/SocialButton.tsx +++ b/packages/react/src/components/presentation/options/SocialButton.tsx @@ -16,48 +16,29 @@ * under the License. */ -import {FC} from 'react'; -import Button from '../../../primitives/Button/Button'; -import {BaseSignInOptionProps} from './SignInOptionFactory'; -import useTranslation from '../../../../hooks/useTranslation'; +import {FC, HTMLAttributes} from 'react'; +import Button from '../../primitives/Button/Button'; +import {BaseSignInOptionProps} from '../SignIn/options/SignInOptionFactory'; +import useTranslation from '../../../hooks/useTranslation'; /** * Social Login Sign-In Option Component. * Handles authentication with external identity providers (Google, GitHub, etc.). */ -const SocialLogin: FC = ({ - authenticator, +const SocialLogin: FC> = ({ isLoading, - onSubmit, - buttonClassName = '', preferences, + children, + ...rest }) => { const {t} = useTranslation(preferences?.i18n); - - /** - * Get display name for the social provider. - */ - const getDisplayName = (): string => { - const providerName = authenticator.idp; - return t('elements.buttons.social', {connection: providerName}); - }; - - /** - * Handle button click. - */ - const handleClick = () => { - onSubmit(authenticator); - }; - return ( ); }; From cd77702cb77cc3dcb833d3b860fb327844bd9dc2 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 18 Sep 2025 17:36:01 +0530 Subject: [PATCH 2/9] chore(nextjs): remove isDarkMode state and useMemo for theme mode handling --- .../src/client/contexts/Asgardeo/AsgardeoProvider.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index 2946ea7a..82c07d8c 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -104,7 +104,6 @@ const AsgardeoClientProvider: FC> const reRenderCheckRef: RefObject = useRef(false); const router = useRouter(); const searchParams = useSearchParams(); - const [isDarkMode, setIsDarkMode] = useState(false); const [isLoading, setIsLoading] = useState(true); const [user, setUser] = useState(_user); const [userProfile, setUserProfile] = useState(_userProfile); @@ -175,12 +174,11 @@ const AsgardeoClientProvider: FC> })(); }, []); - useEffect(() => { + const isDarkMode: boolean = useMemo(() => { if (!preferences?.theme?.mode || preferences.theme.mode === 'system') { - setIsDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches); - } else { - setIsDarkMode(preferences.theme.mode === 'dark'); + return window.matchMedia('(prefers-color-scheme: dark)').matches; } + return preferences.theme.mode === 'dark'; }, [preferences?.theme?.mode]); useEffect(() => { From 06227347dfb5d38056763682cdf7a5e64b360623 Mon Sep 17 00:00:00 2001 From: Brion Date: Thu, 18 Sep 2025 17:36:33 +0530 Subject: [PATCH 3/9] chore(react): add initialFocus prop to FloatingFocusManager in multiple components --- .../BaseOrganizationSwitcher.tsx | 2 +- .../presentation/UserDropdown/BaseUserDropdown.tsx | 2 +- .../src/components/primitives/Dialog/Dialog.tsx | 2 +- packages/react/src/index.ts | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react/src/components/presentation/OrganizationSwitcher/BaseOrganizationSwitcher.tsx b/packages/react/src/components/presentation/OrganizationSwitcher/BaseOrganizationSwitcher.tsx index 44903971..0a0a6357 100644 --- a/packages/react/src/components/presentation/OrganizationSwitcher/BaseOrganizationSwitcher.tsx +++ b/packages/react/src/components/presentation/OrganizationSwitcher/BaseOrganizationSwitcher.tsx @@ -313,7 +313,7 @@ export const BaseOrganizationSwitcher: FC = ({ {isOpen && ( - +
{/* Header - Current Organization */} {currentOrganization && ( diff --git a/packages/react/src/components/presentation/UserDropdown/BaseUserDropdown.tsx b/packages/react/src/components/presentation/UserDropdown/BaseUserDropdown.tsx index 1e0e6579..25166955 100644 --- a/packages/react/src/components/presentation/UserDropdown/BaseUserDropdown.tsx +++ b/packages/react/src/components/presentation/UserDropdown/BaseUserDropdown.tsx @@ -231,7 +231,7 @@ export const BaseUserDropdown: FC = ({ {isOpen && ( - +
- +
Date: Fri, 19 Sep 2025 09:19:51 +0530 Subject: [PATCH 4/9] feat(react): enhance button styles with focus states and add isLoading prop to user profile components --- packages/javascript/src/theme/createTheme.ts | 8 +- .../UserProfile/BaseUserProfile.tsx | 5 +- .../presentation/UserProfile/UserProfile.tsx | 2 +- .../presentation/options/MicrosoftButton.tsx | 2 +- .../options/SignInWithEthereumButton.tsx | 2 +- .../presentation/options/SocialButton.tsx | 2 +- .../primitives/Button/Button.styles.ts | 73 +++++++++++++++++++ 7 files changed, 85 insertions(+), 9 deletions(-) diff --git a/packages/javascript/src/theme/createTheme.ts b/packages/javascript/src/theme/createTheme.ts index 6a80e030..6dbae05e 100644 --- a/packages/javascript/src/theme/createTheme.ts +++ b/packages/javascript/src/theme/createTheme.ts @@ -142,15 +142,15 @@ const lightTheme: ThemeConfig = { const darkTheme: ThemeConfig = { colors: { action: { - active: 'rgba(255, 255, 255, 0.70)', - hover: 'rgba(255, 255, 255, 0.04)', + active: '#1c1c1c', + hover: '#1c1c1c', hoverOpacity: 0.04, - selected: 'rgba(255, 255, 255, 0.08)', + selected: '#1c1c1c', selectedOpacity: 0.08, disabled: 'rgba(255, 255, 255, 0.26)', disabledBackground: 'rgba(255, 255, 255, 0.12)', disabledOpacity: 0.38, - focus: 'rgba(255, 255, 255, 0.12)', + focus: '#1c1c1c', focusOpacity: 0.12, activatedOpacity: 0.12, }, diff --git a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx index a2d72675..18719842 100644 --- a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx +++ b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx @@ -75,6 +75,7 @@ export interface BaseUserProfileProps { schemas?: Schema[]; title?: string; error?: string | null; + isLoading?: boolean; } // Fields to skip based on schema.name @@ -117,6 +118,7 @@ const BaseUserProfile: FC = ({ onUpdate, open = false, error = null, + isLoading = false, }): ReactElement => { const {theme, colorScheme} = useTheme(); const [editedUser, setEditedUser] = useState(flattenedProfile || profile); @@ -524,7 +526,7 @@ const BaseUserProfile: FC = ({
diff --git a/packages/react/src/components/presentation/UserProfile/UserProfile.tsx b/packages/react/src/components/presentation/UserProfile/UserProfile.tsx index c616d48d..4ca4e2bf 100644 --- a/packages/react/src/components/presentation/UserProfile/UserProfile.tsx +++ b/packages/react/src/components/presentation/UserProfile/UserProfile.tsx @@ -54,7 +54,7 @@ export type UserProfileProps = Omit = ({...rest}: UserProfileProps): ReactElement => { - const {baseUrl} = useAsgardeo(); + const {baseUrl, isLoading} = useAsgardeo(); const {profile, flattenedProfile, schemas, onUpdateProfile} = useUser(); const {t} = useTranslation(); diff --git a/packages/react/src/components/presentation/options/MicrosoftButton.tsx b/packages/react/src/components/presentation/options/MicrosoftButton.tsx index 4e2cf69f..dfd8c10f 100644 --- a/packages/react/src/components/presentation/options/MicrosoftButton.tsx +++ b/packages/react/src/components/presentation/options/MicrosoftButton.tsx @@ -35,6 +35,7 @@ const MicrosoftButton: FC } - {...rest} > {children ?? t('elements.buttons.microsoft')} diff --git a/packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx b/packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx index 0174f431..34d1d5c8 100644 --- a/packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx +++ b/packages/react/src/components/presentation/options/SignInWithEthereumButton.tsx @@ -35,6 +35,7 @@ const SignInWithEthereumButton: FC } - {...rest} > {children ?? t('elements.buttons.ethereum')} diff --git a/packages/react/src/components/presentation/options/SocialButton.tsx b/packages/react/src/components/presentation/options/SocialButton.tsx index 6516015e..8c20fc14 100644 --- a/packages/react/src/components/presentation/options/SocialButton.tsx +++ b/packages/react/src/components/presentation/options/SocialButton.tsx @@ -34,6 +34,7 @@ const SocialLogin: FC> const {t} = useTranslation(preferences?.i18n); return ( diff --git a/packages/react/src/components/primitives/Button/Button.styles.ts b/packages/react/src/components/primitives/Button/Button.styles.ts index 282d420d..ccc27cfe 100644 --- a/packages/react/src/components/primitives/Button/Button.styles.ts +++ b/packages/react/src/components/primitives/Button/Button.styles.ts @@ -128,6 +128,10 @@ const useStyles = ( background-color: ${theme.vars.colors.primary.main}; opacity: 0.8; } + &:focus:not(:disabled) { + background-color: ${theme.vars.colors.primary.main}; + opacity: 0.8; + } `, 'primary-outline': css` background-color: transparent; @@ -142,30 +146,50 @@ const useStyles = ( color: ${theme.vars.colors.primary.contrastText}; opacity: 0.9; } + &:focus:not(:disabled) { + background-color: ${theme.vars.colors.primary.main}; + color: ${theme.vars.colors.primary.contrastText}; + opacity: 0.9; + } `, 'primary-text': css` background-color: transparent; color: ${theme.vars.colors.primary.main}; border-color: transparent; &:hover:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.hover}; } &:active:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.selected}; } + &:focus:not(:disabled) { + border-color: transparent; + background-color: ${theme.vars.colors.action.focus}; + outline: none; + } `, 'primary-icon': css` background-color: transparent; color: ${theme.vars.colors.primary.main}; border-color: transparent; &:hover:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.hover}; color: ${theme.vars.colors.primary.dark}; } &:active:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.selected}; color: ${theme.vars.colors.primary.dark}; } + &:focus:not(:disabled) { + border-color: transparent; + background-color: ${theme.vars.colors.action.focus}; + color: ${theme.vars.colors.primary.dark}; + outline: none; + } `, 'secondary-solid': css` background-color: ${theme.vars.colors.secondary.main}; @@ -179,6 +203,10 @@ const useStyles = ( background-color: ${theme.vars.colors.secondary.main}; opacity: 0.8; } + &:focus:not(:disabled) { + background-color: ${theme.vars.colors.secondary.main}; + opacity: 0.8; + } `, 'secondary-outline': css` background-color: transparent; @@ -193,30 +221,50 @@ const useStyles = ( color: ${theme.vars.colors.secondary.contrastText}; opacity: 0.9; } + &:focus:not(:disabled) { + background-color: ${theme.vars.colors.secondary.main}; + color: ${theme.vars.colors.secondary.contrastText}; + opacity: 0.9; + } `, 'secondary-text': css` background-color: transparent; color: ${theme.vars.colors.secondary.main}; border-color: transparent; &:hover:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.hover}; } &:active:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.selected}; } + &:focus:not(:disabled) { + border-color: transparent; + background-color: ${theme.vars.colors.action.focus}; + outline: none; + } `, 'secondary-icon': css` background-color: transparent; color: ${theme.vars.colors.secondary.main}; border-color: transparent; &:hover:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.hover}; color: ${theme.vars.colors.secondary.dark}; } &:active:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.selected}; color: ${theme.vars.colors.secondary.dark}; } + &:focus:not(:disabled) { + border-color: transparent; + background-color: ${theme.vars.colors.action.focus}; + color: ${theme.vars.colors.secondary.dark}; + outline: none; + } `, 'tertiary-solid': css` background-color: ${theme.vars.colors.text.secondary}; @@ -231,6 +279,11 @@ const useStyles = ( color: ${theme.vars.colors.background.surface}; opacity: 0.9; } + &:focus:not(:disabled) { + background-color: ${theme.vars.colors.text.primary}; + color: ${theme.vars.colors.background.surface}; + opacity: 0.9; + } `, 'tertiary-outline': css` background-color: transparent; @@ -244,32 +297,52 @@ const useStyles = ( background-color: ${theme.vars.colors.action.selected}; border-color: ${theme.vars.colors.text.primary}; } + &:focus:not(:disabled) { + background-color: ${theme.vars.colors.action.focus}; + border-color: ${theme.vars.colors.text.primary}; + } `, 'tertiary-text': css` background-color: transparent; color: ${theme.vars.colors.text.secondary}; border-color: transparent; &:hover:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.hover}; color: ${theme.vars.colors.text.primary}; } &:active:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.selected}; color: ${theme.vars.colors.text.primary}; } + &:focus:not(:disabled) { + border-color: transparent; + background-color: ${theme.vars.colors.action.focus}; + color: ${theme.vars.colors.text.primary}; + outline: none; + } `, 'tertiary-icon': css` background-color: transparent; color: ${theme.vars.colors.text.secondary}; border-color: transparent; &:hover:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.hover}; color: ${theme.vars.colors.text.primary}; } &:active:not(:disabled) { + border-color: transparent; background-color: ${theme.vars.colors.action.selected}; color: ${theme.vars.colors.text.primary}; } + &:focus:not(:disabled) { + border-color: transparent; + background-color: ${theme.vars.colors.action.focus}; + color: ${theme.vars.colors.text.primary}; + outline: none; + } `, }; From 4e9a7c02a09cbf9ba28bf777643273774e938c65 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 19 Sep 2025 09:19:57 +0530 Subject: [PATCH 5/9] feat(react): add loading state to Avatar component and default user icon --- .../primitives/Avatar/Avatar.styles.ts | 8 ++++++ .../components/primitives/Avatar/Avatar.tsx | 25 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/primitives/Avatar/Avatar.styles.ts b/packages/react/src/components/primitives/Avatar/Avatar.styles.ts index 9c35e20f..06fe5db3 100644 --- a/packages/react/src/components/primitives/Avatar/Avatar.styles.ts +++ b/packages/react/src/components/primitives/Avatar/Avatar.styles.ts @@ -88,11 +88,19 @@ const useStyles = ( } `; + const iconStyles = css` + width: 60%; + height: 60%; + fill: ${backgroundColor ? '#ffffff' : theme.vars.colors.text.secondary}; + opacity: 0.8; + `; + return { avatar: baseAvatar, variant: variantStyles[variant], image: imageStyles, skeleton: skeletonStyles, + icon: iconStyles, }; }, [theme, colorScheme, size, variant, backgroundColor]); }; diff --git a/packages/react/src/components/primitives/Avatar/Avatar.tsx b/packages/react/src/components/primitives/Avatar/Avatar.tsx index 935e6c1c..d6680cbb 100644 --- a/packages/react/src/components/primitives/Avatar/Avatar.tsx +++ b/packages/react/src/components/primitives/Avatar/Avatar.tsx @@ -56,6 +56,10 @@ export interface AvatarProps { * @default 'circular' */ variant?: 'circular' | 'square'; + /** + * Loading state of the avatar + */ + isLoading?: boolean; } export const Avatar: FC = ({ @@ -66,6 +70,7 @@ export const Avatar: FC = ({ name, size = 64, variant = 'circular', + isLoading = false, }): JSX.Element => { const {theme, colorScheme} = useTheme(); @@ -112,6 +117,9 @@ export const Avatar: FC = ({ const styles = useStyles(theme, colorScheme, size, variant, backgroundColor); + // Determine if we're in the default state (no image, no name, not loading) + const isDefaultState = !imageUrl && !name && !isLoading; + const getInitials = (fullName: string): string => fullName .split(' ') @@ -126,11 +134,25 @@ export const Avatar: FC = ({ {alt} ); } + if (name) { return getInitials(name); } - return
; + if (isLoading) { + return
; + } + + // Default user icon + return ( + + + + ); }; return ( @@ -140,6 +162,7 @@ export const Avatar: FC = ({ styles.avatar, styles.variant, withVendorCSSClassPrefix(bem('avatar', null, variant)), + isDefaultState && withVendorCSSClassPrefix(bem('avatar', 'default')), className, )} > From eafd14620e8a5e155c8cd4a5cb3452775281d8d0 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 19 Sep 2025 10:05:31 +0530 Subject: [PATCH 6/9] feat(react): add showFields and hideFields props to UserProfile for field visibility control --- .../UserProfile/BaseUserProfile.tsx | 38 ++++++++++++++++++- .../presentation/UserProfile/UserProfile.tsx | 10 +++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx index 18719842..1dfe2da4 100644 --- a/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx +++ b/packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx @@ -67,12 +67,14 @@ export interface BaseUserProfileProps { editable?: boolean; fallback?: ReactElement; flattenedProfile?: User; + hideFields?: string[]; mode?: 'inline' | 'popup'; onOpenChange?: (open: boolean) => void; onUpdate?: (payload: any) => Promise; open?: boolean; profile?: User; schemas?: Schema[]; + showFields?: string[]; title?: string; error?: string | null; isLoading?: boolean; @@ -119,12 +121,44 @@ const BaseUserProfile: FC = ({ open = false, error = null, isLoading = false, + showFields = [], + hideFields = [], }): ReactElement => { const {theme, colorScheme} = useTheme(); const [editedUser, setEditedUser] = useState(flattenedProfile || profile); const [editingFields, setEditingFields] = useState>({}); const {t} = useTranslation(); + /** + * Determines if a field should be visible based on showFields, hideFields, and fieldsToSkip arrays. + * Priority order: + * 1. fieldsToSkip (always hidden) - highest priority + * 2. hideFields (explicitly hidden) + * 3. showFields (explicitly shown, if array is not empty) + * 4. Default behavior (show all fields not in fieldsToSkip) + */ + const shouldShowField = useCallback( + (fieldName: string): boolean => { + // Always skip fields in the hardcoded fieldsToSkip array + if (fieldsToSkip.includes(fieldName)) { + return false; + } + + // If hideFields is provided and contains this field, hide it + if (hideFields.length > 0 && hideFields.includes(fieldName)) { + return false; + } + + // If showFields is provided and not empty, only show fields in that array + if (showFields.length > 0) { + return showFields.includes(fieldName); + } + + return true; + }, + [showFields, hideFields], + ); + const PencilIcon = () => ( = ({ const profileEntries = Object.entries(currentUser) .filter(([key, value]) => { - if (fieldsToSkip.includes(key)) return false; + if (!shouldShowField(key)) return false; return value !== undefined && value !== '' && value !== null; }) @@ -612,7 +646,7 @@ const BaseUserProfile: FC = ({ {schemas && schemas.length > 0 ? schemas .filter(schema => { - if (fieldsToSkip.includes(schema.name)) return false; + if (!schema.name || !shouldShowField(schema.name)) return false; if (!editable) { const value = flattenedProfile && schema.name ? flattenedProfile[schema.name] : undefined; diff --git a/packages/react/src/components/presentation/UserProfile/UserProfile.tsx b/packages/react/src/components/presentation/UserProfile/UserProfile.tsx index 4ca4e2bf..2f7fdb95 100644 --- a/packages/react/src/components/presentation/UserProfile/UserProfile.tsx +++ b/packages/react/src/components/presentation/UserProfile/UserProfile.tsx @@ -51,6 +51,16 @@ export type UserProfileProps = OmitPlease sign in to view your profile
} * /> + * + * // With field filtering - only show specific fields + * + * + * // With field hiding - hide specific fields + * * ``` */ const UserProfile: FC = ({...rest}: UserProfileProps): ReactElement => { From 9e57229f0e358cb430e8eecad63ee296ec82bdd9 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 19 Sep 2025 10:05:52 +0530 Subject: [PATCH 7/9] feat(react): add DEFAULT_THEME constant and update theme mode handling in AsgardeoProvider and ThemeProvider --- packages/javascript/src/index.ts | 2 +- packages/javascript/src/theme/createTheme.ts | 8 +++++--- packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx | 6 ++++-- packages/react/src/contexts/Theme/ThemeProvider.tsx | 3 ++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index 2c9e3217..38c40290 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -119,7 +119,7 @@ export {I18nBundle, I18nTranslations, I18nMetadata} from './models/i18n'; export {default as AsgardeoJavaScriptClient} from './AsgardeoJavaScriptClient'; -export {default as createTheme} from './theme/createTheme'; +export {default as createTheme, DEFAULT_THEME} from './theme/createTheme'; export {ThemeColors, ThemeConfig, Theme, ThemeMode, ThemeDetection} from './theme/types'; export {default as bem} from './utils/bem'; diff --git a/packages/javascript/src/theme/createTheme.ts b/packages/javascript/src/theme/createTheme.ts index 6dbae05e..bb7cb92e 100644 --- a/packages/javascript/src/theme/createTheme.ts +++ b/packages/javascript/src/theme/createTheme.ts @@ -32,7 +32,7 @@ * under the License. */ -import {Theme, ThemeConfig, ThemeVars} from './types'; +import {Theme, ThemeConfig, ThemeMode, ThemeVars} from './types'; import {RecursivePartial} from '../models/utility-types'; import VendorConstants from '../constants/VendorConstants'; @@ -57,7 +57,7 @@ const lightTheme: ThemeConfig = { dark: '#174ea6', }, secondary: { - main: '#424242', + main: '#8b8b8b', contrastText: '#ffffff', dark: '#212121', }, @@ -160,7 +160,7 @@ const darkTheme: ThemeConfig = { dark: '#174ea6', }, secondary: { - main: '#424242', + main: '#8b8b8b', contrastText: '#ffffff', dark: '#212121', }, @@ -655,4 +655,6 @@ const createTheme = (config: RecursivePartial = {}, isDark = false) }; }; +export const DEFAULT_THEME: ThemeMode = 'light'; + export default createTheme; diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx index 9599def0..06962c47 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx @@ -27,6 +27,7 @@ import { GetBrandingPreferenceConfig, BrandingPreference, IdToken, + DEFAULT_THEME, } from '@asgardeo/browser'; import {FC, RefObject, PropsWithChildren, ReactElement, useEffect, useMemo, useRef, useState, useCallback} from 'react'; import AsgardeoContext from './AsgardeoContext'; @@ -399,10 +400,11 @@ const AsgardeoProvider: FC> = ({ }; const isDarkMode: boolean = useMemo(() => { - if (!preferences?.theme?.mode || preferences.theme.mode === 'system') { + if (preferences.theme.mode === 'system') { return window.matchMedia('(prefers-color-scheme: dark)').matches; } - return preferences.theme.mode === 'dark'; + + return preferences.theme.mode === DEFAULT_THEME; }, [preferences?.theme?.mode]); const handleProfileUpdate = (payload: User): void => { diff --git a/packages/react/src/contexts/Theme/ThemeProvider.tsx b/packages/react/src/contexts/Theme/ThemeProvider.tsx index 8494dcc1..752e307a 100644 --- a/packages/react/src/contexts/Theme/ThemeProvider.tsx +++ b/packages/react/src/contexts/Theme/ThemeProvider.tsx @@ -28,6 +28,7 @@ import { createMediaQueryListener, BrowserThemeDetection, ThemePreferences, + DEFAULT_THEME, } from '@asgardeo/browser'; import ThemeContext from './ThemeContext'; import useBrandingContext from '../Branding/useBrandingContext'; @@ -109,7 +110,7 @@ const applyThemeToDOM = (theme: Theme) => { const ThemeProvider: FC> = ({ children, theme: themeConfig, - mode = 'system', + mode = DEFAULT_THEME, detection = {}, inheritFromBranding = true, }: PropsWithChildren): ReactElement => { From 89119e35991802ed562aac2f3dcfc7e8baa1e608 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 19 Sep 2025 10:13:58 +0530 Subject: [PATCH 8/9] fix(react): update secondary color in light theme and remove dialog component width constraints --- packages/javascript/src/theme/createTheme.ts | 2 +- .../src/components/primitives/Dialog/Dialog.styles.ts | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/javascript/src/theme/createTheme.ts b/packages/javascript/src/theme/createTheme.ts index bb7cb92e..e3b28086 100644 --- a/packages/javascript/src/theme/createTheme.ts +++ b/packages/javascript/src/theme/createTheme.ts @@ -57,7 +57,7 @@ const lightTheme: ThemeConfig = { dark: '#174ea6', }, secondary: { - main: '#8b8b8b', + main: '#424242', contrastText: '#ffffff', dark: '#212121', }, diff --git a/packages/react/src/components/primitives/Dialog/Dialog.styles.ts b/packages/react/src/components/primitives/Dialog/Dialog.styles.ts index 77ef2ae2..afc29a27 100644 --- a/packages/react/src/components/primitives/Dialog/Dialog.styles.ts +++ b/packages/react/src/components/primitives/Dialog/Dialog.styles.ts @@ -41,17 +41,6 @@ const useStyles = (theme: Theme, colorScheme: string) => { border-radius: ${theme.vars.borderRadius.large}; box-shadow: 0 2px 8px ${colorScheme === 'dark' ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.15)'}; outline: none; - max-width: 35vw; - min-width: 35vw; - @media (max-width: 900px) { - max-width: 80vw; - min-width: 80vw; - } - @media (max-width: 600px) { - max-width: 95vw; - min-width: 95vw; - } - max-height: 90vh; overflow-y: auto; z-index: 10000; `; From dc2a6323e0a8dabe481ff08885f041f838001a41 Mon Sep 17 00:00:00 2001 From: Brion Date: Fri, 19 Sep 2025 11:59:02 +0530 Subject: [PATCH 9/9] feat(react): integrate getActiveTheme function for dynamic theme handling across components --- packages/browser/src/__legacy__/client.ts | 3 ++ packages/browser/src/index.ts | 1 + packages/browser/src/theme/getActiveTheme.ts | 54 +++++++++++++++++++ packages/javascript/src/index.ts | 13 +++-- .../contexts/Asgardeo/AsgardeoProvider.tsx | 7 ++- .../actions/SignInButton/SignInButton.tsx | 2 +- .../contexts/Asgardeo/AsgardeoProvider.tsx | 12 +---- packages/react/src/index.ts | 2 +- 8 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 packages/browser/src/theme/getActiveTheme.ts diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index 5e971e62..d47a8950 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -411,6 +411,9 @@ export class AsgardeoSPAClient { } return response; + }) + .catch(error => { + return Promise.reject(error) }); } diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 0131b741..2c70af16 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -58,5 +58,6 @@ export { createMediaQueryListener, BrowserThemeDetection, } from './theme/themeDetection'; +export {default as getActiveTheme} from './theme/getActiveTheme'; export {default as http} from './utils/http'; diff --git a/packages/browser/src/theme/getActiveTheme.ts b/packages/browser/src/theme/getActiveTheme.ts new file mode 100644 index 00000000..cc1e85a6 --- /dev/null +++ b/packages/browser/src/theme/getActiveTheme.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 {DEFAULT_THEME, ThemeMode} from '@asgardeo/javascript'; +import {BrowserThemeDetection, detectThemeMode} from './themeDetection'; + +/** + * Gets the active theme based on the theme mode preference + * @param mode - The theme mode preference ('light', 'dark', 'system', or 'class') + * @param config - Additional configuration for theme detection + * @returns 'light' or 'dark' based on the resolved theme + */ +const getActiveTheme = (mode: ThemeMode, config: BrowserThemeDetection = {}): ThemeMode => { + if (mode === 'dark') { + return 'dark'; + } + + if (mode === 'light') { + return 'light'; + } + + if (mode === 'system') { + if (typeof window !== 'undefined' && window.matchMedia) { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + // Default to light mode if system detection is not available + return DEFAULT_THEME; + } + + if (mode === 'class') { + return detectThemeMode(mode, config); + } + + // Default to light mode for any unknown mode + return DEFAULT_THEME; +}; + +export default getActiveTheme; diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index 38c40290..03feb6a2 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -16,10 +16,17 @@ * under the License. */ -export * from './__legacy__/client'; -export * from './__legacy__/models'; +export {AsgardeoAuthClient} from './__legacy__/client'; +export { + DefaultAuthClientConfig, + WellKnownAuthClientConfig, + BaseURLAuthClientConfig, + ExplicitAuthClientConfig, + StrictAuthClientConfig, + AuthClientConfig, +} from './__legacy__/models'; -export * from './IsomorphicCrypto'; +export {IsomorphicCrypto} from './IsomorphicCrypto'; export {default as initializeEmbeddedSignInFlow} from './api/initializeEmbeddedSignInFlow'; export {default as executeEmbeddedSignInFlow} from './api/executeEmbeddedSignInFlow'; diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index 82c07d8c..b6ff0c37 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -40,6 +40,7 @@ import { AsgardeoProviderProps, OrganizationProvider, BrandingProvider, + getActiveTheme, } from '@asgardeo/react'; import {useRouter, useSearchParams} from 'next/navigation'; import {FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef, useState} from 'react'; @@ -308,7 +309,11 @@ const AsgardeoClientProvider: FC> - + > = ({ } }; - const isDarkMode: boolean = useMemo(() => { - if (preferences.theme.mode === 'system') { - return window.matchMedia('(prefers-color-scheme: dark)').matches; - } - - return preferences.theme.mode === DEFAULT_THEME; - }, [preferences?.theme?.mode]); - const handleProfileUpdate = (payload: User): void => { setUser(payload); setUserProfile(prev => ({ @@ -476,7 +468,7 @@ const AsgardeoProvider: FC> = ({ diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 18a03ad5..b740f8ad 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -273,4 +273,4 @@ export {default as updateMeProfile, UpdateMeProfileConfig} from './api/updateMeP export {default as getMeProfile} from './api/getScim2Me'; export * from './api/getScim2Me'; -export {AsgardeoRuntimeError, http} from '@asgardeo/browser'; +export {AsgardeoRuntimeError, http, getActiveTheme} from '@asgardeo/browser';