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
6 changes: 6 additions & 0 deletions .changeset/metal-lemons-pick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@asgardeo/javascript': patch
'@asgardeo/react': patch
---

Improve `<LanguageSwitcher />` & other components
6 changes: 3 additions & 3 deletions packages/javascript/src/models/v2/embedded-flow-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ export enum EmbeddedFlowActionVariant {
/** Link-styled action button */
Link = 'LINK',

/** Outlined action button for secondary emphasis */
Outlined = 'OUTLINED',

/** Primary action button with highest visual emphasis */
Primary = 'PRIMARY',

/** Secondary action button with moderate visual emphasis */
Secondary = 'SECONDARY',

/** Social media action button (e.g., Google, Facebook) */
Social = 'SOCIAL',

/** Success action button for positive confirmations */
Success = 'SUCCESS',

Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/adapters/SubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const ButtonComponent: FC<AdapterProps> = ({
case 'TEXT':
return {color: 'primary' as const, variant: 'text' as const};
case 'SOCIAL':
case 'OUTLINED':
return {color: 'primary' as const, variant: 'outline' as const};
default:
return {color: 'primary' as const, variant: 'solid' as const};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
useInteractions,
useRole,
} from '@floating-ui/react';
import {FC, ReactElement, ReactNode, useState} from 'react';
import {FC, ReactElement, ReactNode, useEffect, useState} from 'react';
import useStyles from './BaseLanguageSwitcher.styles';
import useTheme from '../../../contexts/Theme/useTheme';
import Check from '../../primitives/Icons/Check';
Expand Down Expand Up @@ -109,6 +109,13 @@ const BaseLanguageSwitcher: FC<BaseLanguageSwitcherProps> = ({
const {theme, colorScheme} = useTheme();
const styles: Record<string, string> = useStyles(theme, colorScheme);
const [isOpen, setIsOpen] = useState(false);
const hasMultipleLanguages: boolean = languages.length > 1;

useEffect(() => {
if (!hasMultipleLanguages && isOpen) {
setIsOpen(false);
}
}, [hasMultipleLanguages, isOpen]);

const {refs, floatingStyles, context} = useFloating({
middleware: [offset(4), flip(), shift()],
Expand All @@ -117,9 +124,9 @@ const BaseLanguageSwitcher: FC<BaseLanguageSwitcherProps> = ({
whileElementsMounted: autoUpdate,
});

const click: ReturnType<typeof useClick> = useClick(context);
const dismiss: ReturnType<typeof useDismiss> = useDismiss(context);
const role: ReturnType<typeof useRole> = useRole(context, {role: 'listbox'});
const click: ReturnType<typeof useClick> = useClick(context, {enabled: hasMultipleLanguages});
const dismiss: ReturnType<typeof useDismiss> = useDismiss(context, {enabled: hasMultipleLanguages});
const role: ReturnType<typeof useRole> = useRole(context, {enabled: hasMultipleLanguages, role: 'listbox'});
const {getReferenceProps, getFloatingProps} = useInteractions([click, dismiss, role]);

const currentOption: LanguageOption | undefined = languages.find((l: LanguageOption) => l.code === currentLanguage);
Expand Down Expand Up @@ -149,10 +156,10 @@ const BaseLanguageSwitcher: FC<BaseLanguageSwitcherProps> = ({
>
{currentOption && <span className={styles['triggerEmoji']}>{currentOption.emoji}</span>}
<span className={styles['triggerLabel']}>{currentOption?.displayName ?? currentLanguage}</span>
<ChevronDown />
{hasMultipleLanguages && <ChevronDown />}
</button>

{isOpen && (
{isOpen && hasMultipleLanguages && (
<FloatingPortal>
<FloatingFocusManager context={context} modal={false}>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,22 @@ const LanguageSwitcher: FC<LanguageSwitcherProps> = ({children, className}: Lang
const {currentLanguage} = useTranslation();

const availableLanguageCodes: string[] = meta?.i18n?.languages ?? [];
const effectiveLanguageCodes: string[] = useMemo(() => {
const fallbackCodes: string[] = availableLanguageCodes.length > 0 ? availableLanguageCodes : [currentLanguage];

// Ensure the current language is always resolvable for display label and emoji.
return Array.from(new Set([currentLanguage, ...fallbackCodes]));
}, [availableLanguageCodes, currentLanguage]);

const languages: LanguageOption[] = useMemo(
() =>
availableLanguageCodes.map((code: string) => ({
effectiveLanguageCodes.map((code: string) => ({
code,
displayName: resolveLocaleDisplayName(code, currentLanguage),
// Resolve each label in its own locale so option names stay stable across UI language switches.
displayName: resolveLocaleDisplayName(code, code) || code,
emoji: resolveLocaleEmoji(code),
})),
[availableLanguageCodes, currentLanguage],
[effectiveLanguageCodes],
);

const handleLanguageChange = (language: string): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
EmbeddedFlowComponentV2 as EmbeddedFlowComponent,
EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType,
EmbeddedFlowTextVariantV2 as EmbeddedFlowTextVariant,
EmbeddedFlowActionVariantV2 as EmbeddedFlowActionVariant,
EmbeddedFlowEventTypeV2 as EmbeddedFlowEventType,
createPackageComponentLogger,
resolveVars,
Expand Down Expand Up @@ -110,14 +109,16 @@ const matchesSocialProvider = (
buttonText: string,
provider: string,
authType: AuthType,
componentVariant?: string,
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
_componentVariant?: string,
): boolean => {
const providerId: any = `${provider}_auth`;
const providerMatches: any = actionId === providerId || eventType === providerId;

// For social variant, also check button text for provider name
if (componentVariant?.toUpperCase() === EmbeddedFlowActionVariant.Social) {
return buttonText.toLowerCase().includes(provider);
// Social buttons usually have "Sign in with X" or "Continue with X" text,
// so also check button text for the provider name to increase chances of correct detection (especially for signup flows where action IDs are less standardized)
if (buttonText.toLowerCase().includes(provider)) {
return true;
}

// For signup, also check button text
Expand All @@ -142,6 +143,7 @@ const createAuthComponentFromFlow = (
authType: AuthType,
options: {
buttonClassName?: string;
inStack?: boolean;
inputClassName?: string;
key?: string | number;
/** Flow metadata for resolving {{meta(...)}} expressions at render time */
Expand Down Expand Up @@ -355,16 +357,18 @@ const createAuthComponentFromFlow = (
}

case EmbeddedFlowComponentType.Image: {
const explicitHeight: string = resolve(component.height?.toString());
const explicitWidth: string = resolve(component.width?.toString());
return (
<ImageComponent
key={key}
component={
{
config: {
alt: resolve(component.alt) || resolve(component.label) || 'Image',
height: resolve(component.height.toString()) || 'auto',
height: explicitHeight || (options.inStack ? '50' : 'auto'),
src: resolve(component.src),
width: resolve(component.width.toString()) || '100%',
width: explicitWidth || (options.inStack ? '50' : '100%'),
},
} as any
}
Expand Down Expand Up @@ -418,6 +422,7 @@ const createAuthComponentFromFlow = (
authType,
{
...options,
inStack: true,
key: childComponent.id || `${component.id}_${index}`,
},
),
Expand Down
52 changes: 52 additions & 0 deletions packages/react/src/components/primitives/Icons/ArrowRightLeft.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026, 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 {FC} from 'react';

export interface ArrowRightLeftProps {
/** Color of the icon stroke */
color?: string;
/** Icon size in pixels */
size?: number;
}

/**
* ArrowRightLeft Icon component (lucide-compatible).
*/
const ArrowRightLeft: FC<ArrowRightLeftProps> = ({color = 'currentColor', size = 24}: ArrowRightLeftProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m16 3 4 4-4 4" />
<path d="M20 7H4" />
<path d="m8 21-4-4 4-4" />
<path d="M4 17h16" />
</svg>
);

ArrowRightLeft.displayName = 'ArrowRightLeft';

export default ArrowRightLeft;
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import {FC} from 'react';
import ArrowLeftRight from './ArrowLeftRight';
import ArrowRightLeft from './ArrowRightLeft';

export interface FlowIconProps {
color?: string;
Expand All @@ -30,6 +31,7 @@ export interface FlowIconProps {
*/
const flowIconRegistry: Record<string, FC<FlowIconProps>> = {
ArrowLeftRight,
ArrowRightLeft,
};

export default flowIconRegistry;
Loading