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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SDKs at the same dependency level run concurrently; push conflicts are
# handled by a retry-with-rebase loop in the publish step.

name: 🧩 Release SDKs
name: 🚀 Release

on:
workflow_dispatch:
Expand Down
5 changes: 5 additions & 0 deletions packages/javascript/src/models/embedded-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ export interface EmbeddedFlowComponent {
*/
alt?: string;

/**
* Space-separated list of CSS class names to apply to the rendered element.
*/
classes?: string;

/**
* Icon color, CSS color value (for Icon components).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import useThunderID from '../../../contexts/ThunderID/useThunderID';
* Props for the SignIn component.
* Extends BaseSignInProps for full compatibility with the React BaseSignIn component
*/
export type SignInProps = Pick<BaseSignInProps, 'className' | 'onSuccess' | 'onError' | 'variant' | 'size'>;
export type SignInProps = Pick<BaseSignInProps, 'className' | 'id' | 'onSuccess' | 'onError' | 'variant' | 'size'>;

/**
* A SignIn component for Next.js that provides native authentication flow.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const SignInButton: Component = defineComponent({
BaseSignInButton,
{
class: attrs.class,
id: attrs.id,
isLoading: isLoading.value,
onClick: handleSignIn,
style: attrs.style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const SignOutButton: Component = defineComponent({
BaseSignOutButton,
{
class: attrs.class,
id: attrs.id,
isLoading: isLoading.value,
onClick: handleSignOut,
style: attrs.style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const SignUpButton: Component = defineComponent({
BaseSignUpButton,
{
class: attrs.class,
id: attrs.id,
isLoading: isLoading.value,
onClick: handleSignUp,
style: attrs.style,
Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/components/factories/FieldFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export interface FieldConfig {
* Error message to display.
*/
error?: string;
/**
* HTML id to apply to the rendered field.
*/
id?: string;
/**
* Display name for the field.
*/
Expand Down Expand Up @@ -146,6 +150,7 @@ export const createField = (config: FieldConfig): ReactElement => {
disabled = false,
error,
className,
id,
options = [],
touched = false,
placeholder,
Expand All @@ -158,6 +163,7 @@ export const createField = (config: FieldConfig): ReactElement => {
'data-testid': `thunderid-signin-${name}`,
disabled,
error: validationError,
id,
label,
name,
onBlur,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* under the License.
*/

import {css} from '@emotion/css';
import {css, cx} from '@emotion/css';
import {
FieldType,
FlowMetadataResponse,
Expand Down Expand Up @@ -258,8 +258,9 @@ const createAuthComponentFromFlow = (
const fieldType: string = getFieldType(component.type);

const field: any = createField({
className: options.inputClassName,
className: cx(options.inputClassName, component.classes),
error,
id: component.id,
label: resolve(component.label) || '',
name: identifier,
onBlur: () => options.onInputBlur?.(identifier),
Expand All @@ -280,8 +281,9 @@ const createAuthComponentFromFlow = (
const error: string = isTouched ? formErrors[identifier] : undefined!;

const field: any = createField({
className: options.inputClassName,
className: cx(options.inputClassName, component.classes),
error,
id: component.id,
label: resolve(component.label) || '',
name: identifier,
onBlur: () => options.onInputBlur?.(identifier),
Expand Down Expand Up @@ -346,26 +348,35 @@ const createAuthComponentFromFlow = (

// Render branded social login buttons for known action IDs

const socialButtonClassName: string = cx(options.buttonClassName, component.classes);

if (matchesSocialProvider(actionId, eventType, buttonText, 'google', authType, componentVariant)) {
return <GoogleButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return <GoogleButton key={key} id={component.id} onClick={handleClick} className={socialButtonClassName} />;
}
if (matchesSocialProvider(actionId, eventType, buttonText, 'github', authType, componentVariant)) {
return <GitHubButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return <GitHubButton key={key} id={component.id} onClick={handleClick} className={socialButtonClassName} />;
}
if (matchesSocialProvider(actionId, eventType, buttonText, 'facebook', authType, componentVariant)) {
return <FacebookButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return <FacebookButton key={key} id={component.id} onClick={handleClick} className={socialButtonClassName} />;
}
if (matchesSocialProvider(actionId, eventType, buttonText, 'microsoft', authType, componentVariant)) {
return <MicrosoftButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return <MicrosoftButton key={key} id={component.id} onClick={handleClick} className={socialButtonClassName} />;
}
if (matchesSocialProvider(actionId, eventType, buttonText, 'linkedin', authType, componentVariant)) {
return <LinkedInButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return <LinkedInButton key={key} id={component.id} onClick={handleClick} className={socialButtonClassName} />;
}
if (matchesSocialProvider(actionId, eventType, buttonText, 'ethereum', authType, componentVariant)) {
return <SignInWithEthereumButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return (
<SignInWithEthereumButton
key={key}
id={component.id}
onClick={handleClick}
className={socialButtonClassName}
/>
);
}
if (actionId === 'prompt_mobile' || eventType === 'prompt_mobile') {
return <SmsOtpButton key={key} onClick={handleClick} className={options.buttonClassName} />;
return <SmsOtpButton key={key} id={component.id} onClick={handleClick} className={socialButtonClassName} />;
}

const startIconEl: ReactElement | null = component.startIcon ? (
Expand All @@ -390,14 +401,15 @@ const createAuthComponentFromFlow = (
<Button
fullWidth
key={key}
id={component.id}
onClick={handleClick}
disabled={
isLoading ||
(!isFormValid && !shouldSkipValidation) ||
options.isTimeoutDisabled ||
(component as any).config?.disabled
}
className={options.buttonClassName}
className={cx(options.buttonClassName, component.classes)}
data-testid="thunderid-signin-submit"
variant={component.variant?.toLowerCase() === 'primary' ? 'solid' : 'outline'}
color={component.variant?.toLowerCase() === 'primary' ? 'primary' : 'secondary'}
Expand All @@ -414,6 +426,8 @@ const createAuthComponentFromFlow = (
return (
<Typography
key={key}
id={component.id}
className={component.classes}
variant={variant}
style={{
marginBottom: 2,
Expand Down Expand Up @@ -445,6 +459,7 @@ const createAuthComponentFromFlow = (
return (
<Select
key={key}
id={component.id}
name={identifier}
label={resolve(component.label) || ''}
placeholder={resolve(component.placeholder)}
Expand All @@ -454,7 +469,7 @@ const createAuthComponentFromFlow = (
error={error}
onChange={(e: any): void => onInputChange(identifier, e.target.value)}
onBlur={(): any => options.onInputBlur?.(identifier)}
className={options.inputClassName}
className={cx(options.inputClassName, component.classes)}
/>
);
}
Expand All @@ -468,6 +483,7 @@ const createAuthComponentFromFlow = (
return (
<DatePicker
key={key}
id={component.id}
name={identifier}
label={resolve(component.label) || ''}
placeholder={resolve(component.placeholder)}
Expand All @@ -477,7 +493,7 @@ const createAuthComponentFromFlow = (
error={error}
onChange={(e: any): void => onInputChange(identifier, e.target.value)}
onBlur={(): any => options.onInputBlur?.(identifier)}
className={options.inputClassName}
className={cx(options.inputClassName, component.classes)}
/>
);
}
Expand Down Expand Up @@ -515,7 +531,7 @@ const createAuthComponentFromFlow = (
.filter(Boolean);

return (
<form id={component.id} key={key} style={formStyles}>
<form id={component.id} key={key} className={component.classes} style={formStyles}>
{blockComponents}
</form>
);
Expand Down Expand Up @@ -668,7 +684,7 @@ const createAuthComponentFromFlow = (
: [];

return (
<div key={key} style={stackStyle}>
<div key={key} id={component.id} className={component.classes} style={stackStyle}>
{stackChildren}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ export interface BaseSignInProps {
*/
preferences?: Preferences;

/**
* HTML id attribute for the form container.
*/
id?: string;

/**
* Field-level validation errors returned by the server in `data.fieldErrors` on the
* most recent flow response. The component collapses these into the form's
Expand Down Expand Up @@ -238,6 +243,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
onSubmit,
onError,
error: externalError,
id,
className = '',
inputClassName = '',
buttonClassName = '',
Expand Down Expand Up @@ -526,7 +532,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
};

return (
<div className={containerClasses} data-testid="thunderid-signin">
<div id={id} className={containerClasses} data-testid="thunderid-signin">
{children(renderProps)}
</div>
);
Expand All @@ -535,7 +541,12 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
// Default UI rendering
if (isLoading) {
return (
<CardPrimitive className={cx(containerClasses, styles.card)} data-testid="thunderid-signin" variant={variant}>
<CardPrimitive
id={id}
className={cx(containerClasses, styles.card)}
data-testid="thunderid-signin"
variant={variant}
>
<CardPrimitive.Content>
<div style={{display: 'flex', justifyContent: 'center', padding: '2rem'}}>
<Spinner />
Expand All @@ -547,7 +558,12 @@ const BaseSignInContent: FC<BaseSignInProps> = ({

if (!components || components.length === 0) {
return (
<CardPrimitive className={cx(containerClasses, styles.card)} data-testid="thunderid-signin" variant={variant}>
<CardPrimitive
id={id}
className={cx(containerClasses, styles.card)}
data-testid="thunderid-signin"
variant={variant}
>
<CardPrimitive.Content>
<AlertPrimitive variant="warning">
<Typography variant="body1">{t('errors.signin.components.not.available')}</Typography>
Expand All @@ -558,7 +574,12 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
}

return (
<CardPrimitive className={cx(containerClasses, styles.card)} data-testid="thunderid-signin" variant={variant}>
<CardPrimitive
id={id}
className={cx(containerClasses, styles.card)}
data-testid="thunderid-signin"
variant={variant}
>
<CardPrimitive.Content>
{externalError && (
<div className={styles.flowMessagesContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ export interface BaseSignUpProps {
*/
errorClassName?: string;

/**
* HTML id attribute for the form container.
*/
id?: string;

/**
* Custom CSS class name for form inputs.
*/
Expand Down Expand Up @@ -264,6 +269,7 @@ const BaseSignUpContent: FC<BaseSignUpProps> = ({
onFlowChange,
onComplete,
error: externalError,
id,
className = '',
inputClassName = '',
buttonClassName = '',
Expand Down Expand Up @@ -1099,12 +1105,16 @@ const BaseSignUpContent: FC<BaseSignUpProps> = ({
values: formValues,
};

return <div className={containerClasses}>{children(renderProps)}</div>;
return (
<div id={id} className={containerClasses}>
{children(renderProps)}
</div>
);
}

if (!isFlowInitialized && isLoading) {
return (
<CardPrimitive className={cx(containerClasses, styles.card)} variant={variant}>
<CardPrimitive id={id} className={cx(containerClasses, styles.card)} variant={variant}>
<CardPrimitive.Content>
<div className={styles.loadingContainer}>
<Spinner size="medium" />
Expand All @@ -1116,7 +1126,7 @@ const BaseSignUpContent: FC<BaseSignUpProps> = ({

if (!currentFlow) {
return (
<CardPrimitive className={cx(containerClasses, styles.card)} variant={variant}>
<CardPrimitive id={id} className={cx(containerClasses, styles.card)} variant={variant}>
<CardPrimitive.Content>
<AlertPrimitive variant="error" className={errorClasses}>
<AlertPrimitive.Title>{t('errors.heading')}</AlertPrimitive.Title>
Expand All @@ -1138,7 +1148,7 @@ const BaseSignUpContent: FC<BaseSignUpProps> = ({
);

return (
<CardPrimitive className={cx(containerClasses, styles.card)} variant={variant}>
<CardPrimitive id={id} className={cx(containerClasses, styles.card)} variant={variant}>
{(showTitle || showSubtitle) && (
<CardPrimitive.Header className={styles.header}>
{showTitle && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export interface FormControlProps {
* Custom margin left for helper text (for components like Checkbox)
*/
helperTextMarginLeft?: string;
/**
* HTML id attribute
*/
id?: string;
/**
* Custom container style
*/
Expand All @@ -61,14 +65,15 @@ const FormControl: FC<FormControlProps> = ({
error,
helperText,
className,
id,
helperTextAlign = 'left',
helperTextMarginLeft,
}: FormControlProps) => {
const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();
const styles: Record<string, string> = useStyles(theme, colorScheme, helperTextAlign, helperTextMarginLeft, !!error);

return (
<div className={cx(withVendorCSSClassPrefix(bem('form-control')), styles['formControl'], className)}>
<div id={id} className={cx(withVendorCSSClassPrefix(bem('form-control')), styles['formControl'], className)}>
{children}
{(error || helperText) && (
<Typography
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export interface TypographyProps {
* Whether to disable gutters (margin bottom)
*/
gutterBottom?: boolean;
/**
* HTML id attribute
*/
id?: string;
/**
* Whether the text should be displayed inline
*/
Expand Down
Loading
Loading