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
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {cx} from '@emotion/css';
import {FlowMetadataResponse, WithPreferences, withVendorCSSClassPrefix} from '@thunderid/browser';
import {
ButtonHTMLAttributes,
Expand All @@ -12,6 +11,7 @@ import {
Ref,
RefAttributes,
} from 'react';
import {cx} from '../../../styles/emotion';
import Button from '../../primitives/Button/Button';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {cx} from '@emotion/css';
import {FlowMetadataResponse, WithPreferences, withVendorCSSClassPrefix} from '@thunderid/browser';
import {
forwardRef,
Expand All @@ -12,6 +11,7 @@ import {
Ref,
RefAttributes,
} from 'react';
import {cx} from '../../../styles/emotion';
import Button from '../../primitives/Button/Button';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {cx} from '@emotion/css';
import {FlowMetadataResponse, WithPreferences, withVendorCSSClassPrefix} from '@thunderid/browser';
import {
forwardRef,
Expand All @@ -12,6 +11,7 @@ import {
Ref,
RefAttributes,
} from 'react';
import {cx} from '../../../styles/emotion';
import Button from '../../primitives/Button/Button';

/**
Expand Down
45 changes: 30 additions & 15 deletions packages/react/src/components/adapters/Consent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Toggle from '../primitives/Toggle/Toggle';
import {Info} from '../primitives/Icons';
import Tooltip from '../primitives/Tooltip/Tooltip';
import {UseTranslation} from '../../hooks/useTranslation';
import {css} from '../../styles/emotion';

/**
* Backward-compatible consent purpose type exported by @thunderid/react.
Expand Down Expand Up @@ -112,6 +113,28 @@ const Consent: FC<ConsentProps> = ({
meta,
t,
}: ConsentProps) => {
// Computed per render (not at module scope): a CSP nonce configured on <ThunderIDProvider>
// isn't known until the provider renders, and Emotion needs it applied before any style
// insertion happens. These are static, so Emotion's own cache dedupes the repeat calls to a
// no-op after the first render — this isn't a real per-render cost.
const purposesContainerClass: string = css({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
marginTop: '0.25rem',
});
const purposeItemClass: string = css({paddingBottom: '1rem'});
const sectionClass: string = css({marginTop: '0.5rem'});
const sectionHeaderClass: string = css({alignItems: 'center', display: 'flex', gap: '4px', marginBottom: '10px'});
const optionalSectionHeaderClass: string = css({
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
marginBottom: '10px',
paddingRight: '4px',
});
const optionalSectionLabelClass: string = css({alignItems: 'center', display: 'flex', gap: '4px'});

/** Resolve any remaining {{t()}} or {{meta()}} template expressions in a string at render time. */
const resolve = (text: string | undefined): string => {
if (!text || (!t && !meta)) {
Expand Down Expand Up @@ -172,9 +195,9 @@ const Consent: FC<ConsentProps> = ({
}

return (
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem', marginTop: '0.25rem'}}>
<div className={purposesContainerClass}>
{purposes.map((purpose: ConsentPurposeData, purposeIndex: number) => (
<div key={purpose.purposeId || purposeIndex} style={{paddingBottom: '1rem'}}>
<div key={purpose.purposeId || purposeIndex} className={purposeItemClass}>
{/* TODO: Uncomment when the backend supports multiple purposes for a application */}
{/* <Typography variant="h6" fontWeight={600} gutterBottom color="inherit">
{purpose.purposeName}
Expand All @@ -184,8 +207,8 @@ const Consent: FC<ConsentProps> = ({
</Typography> */}

{purpose.essential && purpose.essential.length > 0 && (
<div style={{marginTop: '0.5rem'}}>
<div style={{display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '10px'}}>
<div className={sectionClass}>
<div className={sectionHeaderClass}>
<Typography variant="subtitle2" fontWeight="bold">
{essentialLabel}
</Typography>
Expand All @@ -205,17 +228,9 @@ const Consent: FC<ConsentProps> = ({
)}

{purpose.optional && purpose.optional.length > 0 && (
<div style={{marginTop: '0.5rem'}}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
paddingRight: '4px',
marginBottom: '10px',
}}
>
<div style={{display: 'flex', alignItems: 'center', gap: '4px'}}>
<div className={sectionClass}>
<div className={optionalSectionHeaderClass}>
<div className={optionalSectionLabelClass}>
<Typography variant="subtitle2" fontWeight="bold">
{purpose.type === 'permissions' ? 'Permissions' : optionalLabel}
</Typography>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {css} from '@emotion/css';
import {Theme} from '@thunderid/browser';
import {useMemo} from 'react';
import {css} from '../../styles/emotion';

const useStyles = (theme: Theme, colorScheme: string): Record<string, string> =>
useMemo(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {cx} from '@emotion/css';
import {type ConsentPurposeData, withVendorCSSClassPrefix, bem} from '@thunderid/browser';
import {type ChangeEvent, FC, ReactNode} from 'react';
import useStyles from './ConsentCheckboxList.styles';
import useTheme from '../../contexts/Theme/useTheme';
import {cx} from '../../styles/emotion';
import Toggle from '../primitives/Toggle/Toggle';
import Typography from '../primitives/Typography/Typography';

Expand Down
41 changes: 25 additions & 16 deletions packages/react/src/components/adapters/ImageComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// SPDX-License-Identifier: Apache-2.0

import {resolveLogoUri, ResolvedLogo} from '@thunderid/browser';
import {CSSProperties, FC, SyntheticEvent} from 'react';
import {FC, SyntheticEvent} from 'react';
import useTheme from '../../contexts/Theme/useTheme';
import {AdapterProps} from '../../models/adapters';
import {css} from '../../styles/emotion';

const DEFAULT_EMOJI_CONTAINER_HEIGHT = '4em';

Expand All @@ -13,19 +14,27 @@ const DEFAULT_EMOJI_CONTAINER_HEIGHT = '4em';
*/
const ImageComponent: FC<AdapterProps> = ({component}: AdapterProps) => {
const {theme} = useTheme();
// Computed per render (not at module scope): a CSP nonce configured on <ThunderIDProvider>
// isn't known until the provider renders, and Emotion needs it applied before any style
// insertion happens. These are static, so Emotion's own cache dedupes the repeat calls to a
// no-op after the first render — this isn't a real per-render cost.
const centerClass: string = css({textAlign: 'center'});
const emojiGlyphClass: string = css({fontSize: '100cqmin', lineHeight: 1});
const config: Record<string, unknown> = component.config || {};
const src: string = (config['src'] as string) || '';
const alt: string = (config['alt'] as string) || (config['label'] as string) || 'Image';
const width: string = (config['width'] as string) || '100%';
const height: string = (config['height'] as string) || 'auto';
const variant: string = component.variant?.toLowerCase() || 'image_block';

const imageStyle: CSSProperties = {
const imageStyle = {
borderRadius: theme.vars.borderRadius.small,
display: 'block',
margin: variant === 'image_block' ? '1rem auto' : '0',
};

const imageClass: string = css(imageStyle);

if (!src) {
return null;
}
Expand All @@ -52,24 +61,24 @@ const ImageComponent: FC<AdapterProps> = ({component}: AdapterProps) => {
containerHeight = DEFAULT_EMOJI_CONTAINER_HEIGHT;
}

const emojiContainerClass: string = css({
...imageStyle,
containerType: 'size',
display: 'inline-grid',
height: containerHeight,
placeItems: 'center',
width: cssWidth,
});

return (
<div key={component.id} style={{textAlign: 'center'}}>
<div key={component.id} className={centerClass}>
{/*
* container-type: size lets the inner span use cqmin (= min(cqw, cqh))
* so the emoji font-size tracks the rendered container dimensions
* rather than the parent's font-size.
*/}
<span
style={{
...imageStyle,
containerType: 'size',
display: 'inline-grid',
height: containerHeight,
placeItems: 'center',
width: cssWidth,
}}
>
<span aria-label={alt} role="img" style={{fontSize: '100cqmin', lineHeight: 1}}>
<span className={emojiContainerClass}>
<span aria-label={alt} role="img" className={emojiGlyphClass}>
{resolvedIcon.glyph}
</span>
</span>
Expand All @@ -78,13 +87,13 @@ const ImageComponent: FC<AdapterProps> = ({component}: AdapterProps) => {
}

return (
<div key={component.id} style={{textAlign: 'center'}}>
<div key={component.id} className={centerClass}>
<img
src={resolvedIcon.imgSrc}
alt={alt}
height={height}
width={width}
style={imageStyle}
className={imageClass}
onError={(e: SyntheticEvent<HTMLImageElement>): void => {
// Hide broken images
e.currentTarget.style.display = 'none';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {css} from '@emotion/css';
import {Theme} from '@thunderid/browser';
import {useMemo} from 'react';
import {css} from '../../../styles/emotion';

const useStyles = (theme: Theme, colorScheme: string): Record<string, string> =>
useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {cx} from '@emotion/css';
import {
autoUpdate,
flip,
Expand All @@ -18,6 +17,7 @@ import {
import {FC, ReactElement, ReactNode, useEffect, useState} from 'react';
import useStyles from './BaseLanguageSwitcher.styles';
import useTheme from '../../../contexts/Theme/useTheme';
import {cx} from '../../../styles/emotion';
import Check from '../../primitives/Icons/Check';
import ChevronDown from '../../primitives/Icons/ChevronDown';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {css} from '@emotion/css';
import {Theme} from '@thunderid/browser';
import {useMemo} from 'react';
import {css} from '../../../styles/emotion';

/**
* Creates styles for the BaseUserDropdown component
Expand Down Expand Up @@ -74,6 +74,10 @@ const useStyles = (theme: Theme, colorScheme: string): Record<string, string> =>
width: 100%;
`;

const menuItemHighlighted: string = css`
background-color: ${theme.vars.colors.action?.hover || 'rgba(0, 0, 0, 0.05)'};
`;

const menuItem: string = css`
display: flex;
align-items: center;
Expand Down Expand Up @@ -195,6 +199,7 @@ const useStyles = (theme: Theme, colorScheme: string): Record<string, string> =>
loadingText,
menuItem,
menuItemAnchor,
menuItemHighlighted,
trigger,
userName,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {cx} from '@emotion/css';
import {
useFloating,
autoUpdate,
Expand All @@ -19,6 +18,7 @@ import {withVendorCSSClassPrefix} from '@thunderid/browser';
import {FC, ReactElement, ReactNode, useState} from 'react';
import useStyles from './BaseUserDropdown.styles';
import useTheme from '../../../contexts/Theme/useTheme';
import {css, cx} from '../../../styles/emotion';
import getDisplayName from '../../../utils/getDisplayName';
import getMappedUserProfileValue from '../../../utils/getMappedUserProfileValue';
import {Avatar} from '../../primitives/Avatar/Avatar';
Expand Down Expand Up @@ -216,13 +216,19 @@ export const BaseUserDropdown: FC<BaseUserDropdownProps> = ({
<FloatingFocusManager context={context} modal={false} initialFocus={-1}>
<div
ref={refs.setFloating}
className={cx(withVendorCSSClassPrefix('user-dropdown__content'), styles['dropdownContent'])}
style={{
...floatingStyles,
// Floating UI doesn't set a z-index by default, so we set a high value to ensure the dropdown appears above other elements.
// https://floating-ui.com/docs/misc#z-index-stacking
zIndex: 9999,
}}
className={cx(
withVendorCSSClassPrefix('user-dropdown__content'),
styles['dropdownContent'],
// Floating UI recomputes the position on every render, so this class is generated
// fresh each time rather than memoized - Emotion still injects it into the shared,
// nonce-tagged `<style>` element, so no inline `style` attribute is needed.
css({
...floatingStyles,
// Floating UI doesn't set a z-index by default, so we set a high value to ensure the dropdown appears above other elements.
// https://floating-ui.com/docs/misc#z-index-stacking
zIndex: 9999,
}),
)}
{...getFloatingProps()}
>
<div className={cx(withVendorCSSClassPrefix('user-dropdown__header'), styles['dropdownHeader'])}>
Expand Down Expand Up @@ -267,13 +273,10 @@ export const BaseUserDropdown: FC<BaseUserDropdownProps> = ({
return (
<a
href={item.href}
style={{
backgroundColor:
hoveredItemIndex === index ? theme.vars.colors.action?.hover : 'transparent',
}}
className={cx(
withVendorCSSClassPrefix('user-dropdown__menu-item'),
styles['menuItemAnchor'],
hoveredItemIndex === index && styles['menuItemHighlighted'],
)}
onMouseEnter={(): void => setHoveredItemIndex(index)}
onMouseLeave={(): void => setHoveredItemIndex(null)}
Expand All @@ -288,11 +291,11 @@ export const BaseUserDropdown: FC<BaseUserDropdownProps> = ({
return (
<Button
onClick={(): void => handleMenuItemClick(item)}
style={{
backgroundColor:
hoveredItemIndex === index ? theme.vars.colors.action?.hover : 'transparent',
}}
className={cx(withVendorCSSClassPrefix('user-dropdown__menu-item'), styles['menuItem'])}
className={cx(
withVendorCSSClassPrefix('user-dropdown__menu-item'),
styles['menuItem'],
hoveredItemIndex === index && styles['menuItemHighlighted'],
)}
color="tertiary"
variant="text"
size="small"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {css} from '@emotion/css';
import {Theme, withVendorCSSClassPrefix} from '@thunderid/browser';
import {useMemo} from 'react';
import {css} from '../../../styles/emotion';

/**
* Creates styles for the BaseUserProfile component
Expand Down
Loading