Skip to content

Commit 1cc08d6

Browse files
authored
Merge pull request #390 from brionmario/refactor-thunder-flows
Improve `<LanguageSwitcher />` & other components
2 parents c70ae80 + c2fb718 commit 1cc08d6

8 files changed

Lines changed: 99 additions & 19 deletions

File tree

.changeset/metal-lemons-pick.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@asgardeo/javascript': patch
3+
'@asgardeo/react': patch
4+
---
5+
6+
Improve `<LanguageSwitcher />` & other components

packages/javascript/src/models/v2/embedded-flow-v2.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,15 @@ export enum EmbeddedFlowActionVariant {
9696
/** Link-styled action button */
9797
Link = 'LINK',
9898

99+
/** Outlined action button for secondary emphasis */
100+
Outlined = 'OUTLINED',
101+
99102
/** Primary action button with highest visual emphasis */
100103
Primary = 'PRIMARY',
101104

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

105-
/** Social media action button (e.g., Google, Facebook) */
106-
Social = 'SOCIAL',
107-
108108
/** Success action button for positive confirmations */
109109
Success = 'SUCCESS',
110110

packages/react/src/components/adapters/SubmitButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const ButtonComponent: FC<AdapterProps> = ({
4747
case 'TEXT':
4848
return {color: 'primary' as const, variant: 'text' as const};
4949
case 'SOCIAL':
50+
case 'OUTLINED':
5051
return {color: 'primary' as const, variant: 'outline' as const};
5152
default:
5253
return {color: 'primary' as const, variant: 'solid' as const};

packages/react/src/components/presentation/LanguageSwitcher/BaseLanguageSwitcher.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
useInteractions,
3131
useRole,
3232
} from '@floating-ui/react';
33-
import {FC, ReactElement, ReactNode, useState} from 'react';
33+
import {FC, ReactElement, ReactNode, useEffect, useState} from 'react';
3434
import useStyles from './BaseLanguageSwitcher.styles';
3535
import useTheme from '../../../contexts/Theme/useTheme';
3636
import Check from '../../primitives/Icons/Check';
@@ -109,6 +109,13 @@ const BaseLanguageSwitcher: FC<BaseLanguageSwitcherProps> = ({
109109
const {theme, colorScheme} = useTheme();
110110
const styles: Record<string, string> = useStyles(theme, colorScheme);
111111
const [isOpen, setIsOpen] = useState(false);
112+
const hasMultipleLanguages: boolean = languages.length > 1;
113+
114+
useEffect(() => {
115+
if (!hasMultipleLanguages && isOpen) {
116+
setIsOpen(false);
117+
}
118+
}, [hasMultipleLanguages, isOpen]);
112119

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

120-
const click: ReturnType<typeof useClick> = useClick(context);
121-
const dismiss: ReturnType<typeof useDismiss> = useDismiss(context);
122-
const role: ReturnType<typeof useRole> = useRole(context, {role: 'listbox'});
127+
const click: ReturnType<typeof useClick> = useClick(context, {enabled: hasMultipleLanguages});
128+
const dismiss: ReturnType<typeof useDismiss> = useDismiss(context, {enabled: hasMultipleLanguages});
129+
const role: ReturnType<typeof useRole> = useRole(context, {enabled: hasMultipleLanguages, role: 'listbox'});
123130
const {getReferenceProps, getFloatingProps} = useInteractions([click, dismiss, role]);
124131

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

155-
{isOpen && (
162+
{isOpen && hasMultipleLanguages && (
156163
<FloatingPortal>
157164
<FloatingFocusManager context={context} modal={false}>
158165
<div

packages/react/src/components/presentation/LanguageSwitcher/LanguageSwitcher.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,22 @@ const LanguageSwitcher: FC<LanguageSwitcherProps> = ({children, className}: Lang
8585
const {currentLanguage} = useTranslation();
8686

8787
const availableLanguageCodes: string[] = meta?.i18n?.languages ?? [];
88+
const effectiveLanguageCodes: string[] = useMemo(() => {
89+
const fallbackCodes: string[] = availableLanguageCodes.length > 0 ? availableLanguageCodes : [currentLanguage];
90+
91+
// Ensure the current language is always resolvable for display label and emoji.
92+
return Array.from(new Set([currentLanguage, ...fallbackCodes]));
93+
}, [availableLanguageCodes, currentLanguage]);
8894

8995
const languages: LanguageOption[] = useMemo(
9096
() =>
91-
availableLanguageCodes.map((code: string) => ({
97+
effectiveLanguageCodes.map((code: string) => ({
9298
code,
93-
displayName: resolveLocaleDisplayName(code, currentLanguage),
99+
// Resolve each label in its own locale so option names stay stable across UI language switches.
100+
displayName: resolveLocaleDisplayName(code, code) || code,
94101
emoji: resolveLocaleEmoji(code),
95102
})),
96-
[availableLanguageCodes, currentLanguage],
103+
[effectiveLanguageCodes],
97104
);
98105

99106
const handleLanguageChange = (language: string): void => {

packages/react/src/components/presentation/auth/AuthOptionFactory.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
EmbeddedFlowComponentV2 as EmbeddedFlowComponent,
2323
EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType,
2424
EmbeddedFlowTextVariantV2 as EmbeddedFlowTextVariant,
25-
EmbeddedFlowActionVariantV2 as EmbeddedFlowActionVariant,
2625
EmbeddedFlowEventTypeV2 as EmbeddedFlowEventType,
2726
createPackageComponentLogger,
2827
resolveVars,
@@ -110,14 +109,16 @@ const matchesSocialProvider = (
110109
buttonText: string,
111110
provider: string,
112111
authType: AuthType,
113-
componentVariant?: string,
112+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
113+
_componentVariant?: string,
114114
): boolean => {
115115
const providerId: any = `${provider}_auth`;
116116
const providerMatches: any = actionId === providerId || eventType === providerId;
117117

118-
// For social variant, also check button text for provider name
119-
if (componentVariant?.toUpperCase() === EmbeddedFlowActionVariant.Social) {
120-
return buttonText.toLowerCase().includes(provider);
118+
// Social buttons usually have "Sign in with X" or "Continue with X" text,
119+
// 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)
120+
if (buttonText.toLowerCase().includes(provider)) {
121+
return true;
121122
}
122123

123124
// For signup, also check button text
@@ -142,6 +143,7 @@ const createAuthComponentFromFlow = (
142143
authType: AuthType,
143144
options: {
144145
buttonClassName?: string;
146+
inStack?: boolean;
145147
inputClassName?: string;
146148
key?: string | number;
147149
/** Flow metadata for resolving {{meta(...)}} expressions at render time */
@@ -355,16 +357,18 @@ const createAuthComponentFromFlow = (
355357
}
356358

357359
case EmbeddedFlowComponentType.Image: {
360+
const explicitHeight: string = resolve(component.height?.toString());
361+
const explicitWidth: string = resolve(component.width?.toString());
358362
return (
359363
<ImageComponent
360364
key={key}
361365
component={
362366
{
363367
config: {
364368
alt: resolve(component.alt) || resolve(component.label) || 'Image',
365-
height: resolve(component.height.toString()) || 'auto',
369+
height: explicitHeight || (options.inStack ? '50' : 'auto'),
366370
src: resolve(component.src),
367-
width: resolve(component.width.toString()) || '100%',
371+
width: explicitWidth || (options.inStack ? '50' : '100%'),
368372
},
369373
} as any
370374
}
@@ -418,6 +422,7 @@ const createAuthComponentFromFlow = (
418422
authType,
419423
{
420424
...options,
425+
inStack: true,
421426
key: childComponent.id || `${component.id}_${index}`,
422427
},
423428
),
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import {FC} from 'react';
20+
21+
export interface ArrowRightLeftProps {
22+
/** Color of the icon stroke */
23+
color?: string;
24+
/** Icon size in pixels */
25+
size?: number;
26+
}
27+
28+
/**
29+
* ArrowRightLeft Icon component (lucide-compatible).
30+
*/
31+
const ArrowRightLeft: FC<ArrowRightLeftProps> = ({color = 'currentColor', size = 24}: ArrowRightLeftProps) => (
32+
<svg
33+
xmlns="http://www.w3.org/2000/svg"
34+
width={size}
35+
height={size}
36+
viewBox="0 0 24 24"
37+
fill="none"
38+
stroke={color}
39+
strokeWidth="2"
40+
strokeLinecap="round"
41+
strokeLinejoin="round"
42+
>
43+
<path d="m16 3 4 4-4 4" />
44+
<path d="M20 7H4" />
45+
<path d="m8 21-4-4 4-4" />
46+
<path d="M4 17h16" />
47+
</svg>
48+
);
49+
50+
ArrowRightLeft.displayName = 'ArrowRightLeft';
51+
52+
export default ArrowRightLeft;

packages/react/src/components/primitives/Icons/flowIconRegistry.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import {FC} from 'react';
2020
import ArrowLeftRight from './ArrowLeftRight';
21+
import ArrowRightLeft from './ArrowRightLeft';
2122

2223
export interface FlowIconProps {
2324
color?: string;
@@ -30,6 +31,7 @@ export interface FlowIconProps {
3031
*/
3132
const flowIconRegistry: Record<string, FC<FlowIconProps>> = {
3233
ArrowLeftRight,
34+
ArrowRightLeft,
3335
};
3436

3537
export default flowIconRegistry;

0 commit comments

Comments
 (0)