Skip to content
Open
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
150 changes: 150 additions & 0 deletions packages/browser/src/branding/attributionDesign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* 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 {bem, withVendorCSSClassPrefix} from '@thunderid/javascript';

/**
* Design tokens for the "Powered by ThunderID" attribution badge.
*
* This is intentionally a fixed brand mark, not a themeable/white-labelable surface:
* the badge always renders in ThunderID's own colors regardless of the host
* application's theme, the same way "Secured by Clerk" or "Powered by Vercel" badges
* stay on-brand for the vendor rather than the tenant. Only the two seam-blending
* values (border + shadow) borrow from the surrounding card's theme.
*
* Framework packages (`@thunderid/react`, `@thunderid/vue`, ...) should render from
* these shared tokens instead of re-deriving the badge's colors, geometry, or mark —
* this file is the single source of truth so the badge stays pixel- and color-identical
* across every framework SDK.
*/
export const ATTRIBUTION_BADGE_COLORS: {background: string; text: string} = {
background: 'rgba(9, 20, 44, 0.98)',
text: 'rgba(139, 249, 250, 0.85)',
};

/**
* The official ThunderID logo mark (icon + wordmark), inverted-for-dark-backgrounds variant.
* The badge's chip is always the fixed dark navy in `ATTRIBUTION_BADGE_COLORS.background`
* regardless of the host theme, so the inverted (white text) logo variant is always correct here.
*/
export const ATTRIBUTION_LOGO_SVG_MARKUP = `<svg viewBox="0 0 1187 257" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M286.259 92.5581V70.7585H391.069V92.5581H351.882V199.098H325.531V92.5581H286.259Z" fill="white"/>
<path d="M434.387 143.381V199.098H408.545V70.7585H433.857V126.836H431.782C434.302 118.807 438.136 112.578 443.284 108.193C448.431 103.807 455.061 101.604 463.216 101.604C469.867 101.604 475.693 103.045 480.649 105.947C485.606 108.85 489.482 113.002 492.236 118.446C494.99 123.891 496.366 130.352 496.366 137.873V199.12H470.439V142.364C470.439 136.39 468.893 131.708 465.843 128.319C462.771 124.929 458.514 123.234 453.049 123.234C449.427 123.234 446.207 124.018 443.368 125.607C440.53 127.196 438.306 129.463 436.738 132.45C435.15 135.437 434.366 139.081 434.366 143.381H434.387Z" fill="white"/>
<path d="M551.525 200.306C544.874 200.306 539.028 198.865 534.05 195.963C529.051 193.061 525.196 188.908 522.463 183.485C519.73 178.062 518.375 171.579 518.375 164.058V102.812H544.217V159.567C544.217 165.541 545.764 170.223 548.877 173.613C551.97 177.002 556.228 178.697 561.629 178.697C565.294 178.697 568.556 177.913 571.352 176.324C574.169 174.735 576.351 172.447 577.939 169.439C579.528 166.431 580.312 162.787 580.312 158.55V102.833H606.239V199.12H581.773L581.435 175.096H582.896C580.375 183.188 576.499 189.417 571.309 193.781C566.12 198.145 559.532 200.327 551.546 200.327L551.525 200.306Z" fill="white"/>
<path d="M654.005 143.381V199.098H628.163V102.812H652.543L652.967 126.836H651.421C653.942 118.807 657.775 112.578 662.923 108.193C668.07 103.807 674.7 101.604 682.855 101.604C689.507 101.604 695.332 103.045 700.288 105.947C705.245 108.85 709.1 113.002 711.833 118.446C714.565 123.87 715.921 130.352 715.921 137.873V199.12H690.078V142.364C690.078 136.39 688.532 131.708 685.482 128.319C682.411 124.929 678.153 123.234 672.688 123.234C669.066 123.234 665.846 124.018 663.007 125.607C660.169 127.196 657.945 129.463 656.377 132.45C654.789 135.437 654.005 139.081 654.005 143.381Z" fill="white"/>
<path d="M773.092 200.751C765.742 200.751 759.09 198.844 753.159 195.031C747.207 191.217 742.526 185.625 739.073 178.231C735.62 170.859 733.905 161.812 733.905 151.135C733.905 140.458 735.684 131.009 739.243 123.658C742.801 116.307 747.546 110.799 753.456 107.112C759.366 103.447 765.89 101.604 773.007 101.604C778.451 101.604 783.005 102.515 786.606 104.358C790.228 106.201 793.151 108.468 795.397 111.159C797.642 113.849 799.294 116.476 800.396 118.997H801.264V70.7585H827.107V199.098H801.709V183.676H800.417C799.273 186.26 797.557 188.866 795.291 191.472C793.024 194.077 790.08 196.281 786.458 198.06C782.836 199.84 778.387 200.73 773.113 200.73L773.092 200.751ZM781.099 179.989C785.462 179.989 789.19 178.782 792.304 176.367C795.397 173.952 797.769 170.583 799.358 166.24C800.968 161.897 801.773 156.834 801.773 151.029C801.773 145.224 800.989 140.098 799.4 135.818C797.812 131.539 795.482 128.213 792.389 125.819C789.296 123.446 785.526 122.239 781.099 122.239C776.672 122.239 772.753 123.467 769.682 125.946C766.61 128.425 764.301 131.793 762.755 136.115C761.209 140.415 760.425 145.394 760.425 151.008C760.425 156.622 761.209 161.622 762.797 165.986C764.386 170.35 766.695 173.761 769.766 176.24C772.838 178.718 776.608 179.947 781.099 179.947V179.989Z" fill="white"/>
<path d="M892.624 201.005C882.816 201.005 874.343 198.992 867.226 194.967C860.109 190.942 854.644 185.243 850.81 177.828C846.997 170.414 845.091 161.643 845.091 151.474C845.091 141.305 846.976 132.852 850.767 125.374C854.559 117.917 859.918 112.091 866.866 107.896C873.814 103.701 881.969 101.604 891.331 101.604C897.644 101.604 903.532 102.6 908.976 104.612C914.42 106.625 919.229 109.655 923.359 113.701C927.49 117.747 930.731 122.811 933.039 128.912C935.369 134.992 936.534 142.174 936.534 150.436V157.681H855.66V141.496H923.867L911.815 145.796C911.815 140.797 911.052 136.454 909.527 132.746C908.002 129.039 905.735 126.179 902.728 124.124C899.72 122.09 895.992 121.073 891.564 121.073C887.137 121.073 883.282 122.111 880.147 124.166C877.012 126.243 874.64 129.039 873.051 132.556C871.441 136.094 870.636 140.119 870.636 144.652V156.114C870.636 161.685 871.568 166.367 873.432 170.159C875.296 173.952 877.902 176.79 881.27 178.676C884.638 180.561 888.557 181.515 893.026 181.515C896.076 181.515 898.83 181.091 901.287 180.222C903.765 179.354 905.863 178.083 907.621 176.388C909.379 174.693 910.713 172.617 911.624 170.138L935.052 174.524C933.505 179.799 930.794 184.438 926.96 188.442C923.105 192.425 918.297 195.518 912.535 197.7C906.773 199.882 900.122 200.963 892.602 200.963L892.624 201.005Z" fill="white"/>
<path d="M954.2 199.098V102.812H979.174V119.612H980.212C981.992 113.637 984.957 109.125 989.13 106.095C993.303 103.066 998.069 101.519 1003.47 101.519C1004.78 101.519 1006.22 101.583 1007.77 101.731C1009.32 101.88 1010.67 102.091 1011.82 102.388V125.48C1010.67 125.077 1009 124.76 1006.82 124.527C1004.64 124.293 1002.56 124.188 1000.61 124.188C996.713 124.188 993.176 125.035 990.02 126.73C986.863 128.425 984.406 130.776 982.648 133.784C980.89 136.793 980.022 140.31 980.022 144.335V199.12H954.179L954.2 199.098Z" fill="white"/>
<path d="M1055.66 70.7797V199.12H1029.4V70.7797H1055.66Z" fill="white"/>
<path d="M1105.17 70.7797V199.12H1078.9V70.7797H1105.17ZM1124.04 199.098H1090.81V176.451H1122.75C1131.01 176.451 1137.96 174.99 1143.55 172.066C1149.14 169.143 1153.36 164.609 1156.17 158.465C1158.99 152.322 1160.39 144.462 1160.39 134.865C1160.39 125.268 1158.97 117.514 1156.13 111.392C1153.29 105.269 1149.1 100.757 1143.55 97.8332C1138 94.9096 1131.14 93.4479 1122.92 93.4479H1090.19V70.8009H1124.46C1137.32 70.8009 1148.4 73.3643 1157.66 78.5123C1166.91 83.6603 1174.07 90.9904 1179.05 100.566C1184.05 110.121 1186.55 121.561 1186.55 134.886C1186.55 148.212 1184.05 159.757 1179.05 169.333C1174.05 178.93 1166.89 186.281 1157.57 191.429C1148.23 196.577 1137.07 199.141 1124.02 199.141L1124.04 199.098Z" fill="white"/>
<path d="M55.4976 26.4391L58.9079 0H0V26.4391H55.4976Z" fill="white"/>
<path d="M39.8651 147.407L49.5666 72.2839H7.95523e-06V256.743H60.5813L80.069 147.407H39.8651Z" fill="#3688FF"/>
<path d="M192.441 59.361C182.803 40.2307 168.95 25.5705 150.924 15.3381C145.522 12.2662 139.782 9.6605 133.724 7.5208L115.422 103.702H159.778L76.3198 256.743H83.3947C109.47 256.743 131.711 251.574 150.161 241.236C168.59 230.897 182.655 216.131 192.378 196.959C202.079 177.765 206.93 154.8 206.93 128.043C206.93 101.286 202.1 78.5123 192.462 59.3821L192.441 59.361Z" fill="#3688FF"/>
</svg>`;

/**
* The logo mark's SVG `viewBox` dimensions, used to derive its rendered width from a
* fixed height (see `buildAttributionCss`) without hardcoding a separately-maintained
* pixel value that could drift from the artwork above.
*/
export const ATTRIBUTION_LOGO_VIEWBOX: {width: number; height: number} = {
width: 1187,
height: 257,
};

/**
* Fixed layout constants for the badge — shared so every framework wrapper attaches
* the badge to its host card identically.
*/
export const ATTRIBUTION_LAYOUT = {
borderRadius: '0 10px 10px 0',
fontSize: '9px',
gap: '6px',
letterSpacing: '0.06em',
logoHeight: '11px',
padding: '10px 8px',
topOffset: '28px',
} as const;

export const ATTRIBUTION_BLOCK: string = withVendorCSSClassPrefix(bem('attribution'));
export const ATTRIBUTION_CONTENT_CLASS: string = withVendorCSSClassPrefix(bem('attribution', 'content'));
export const ATTRIBUTION_LABEL_CLASS: string = withVendorCSSClassPrefix(bem('attribution', 'label'));
export const ATTRIBUTION_LOGO_CLASS: string = withVendorCSSClassPrefix(bem('attribution', 'logo'));

/**
* Builds the shared attribution badge stylesheet as plain CSS text.
*
* The badge's own colors are fixed brand values; `borderColor` and `boxShadow` are the
* only values threaded through from the resolved `Theme` so the badge blends into
* whichever card it's attached to.
*
* @param borderColor - Resolved `theme.vars.colors.border` value of the host theme.
* @param boxShadow - Resolved `theme.vars.shadows.medium` value of the host theme.
* @returns A CSS stylesheet string framework wrappers can inject once and share.
*/
export const buildAttributionCss = (borderColor: string, boxShadow: string): string => `
.${ATTRIBUTION_BLOCK} {
position: absolute;
top: ${ATTRIBUTION_LAYOUT.topOffset};
right: -1px;
transform: translateX(100%);
display: flex;
align-items: center;
justify-content: center;
padding: ${ATTRIBUTION_LAYOUT.padding};
background: ${ATTRIBUTION_BADGE_COLORS.background};
border: 1px solid ${borderColor};
border-left: none;
border-radius: ${ATTRIBUTION_LAYOUT.borderRadius};
box-shadow: ${boxShadow};
text-decoration: none;
z-index: 1;
}

.${ATTRIBUTION_CONTENT_CLASS} {
display: flex;
align-items: center;
gap: ${ATTRIBUTION_LAYOUT.gap};
writing-mode: vertical-rl;
white-space: nowrap;
}

.${ATTRIBUTION_LABEL_CLASS} {
font-size: ${ATTRIBUTION_LAYOUT.fontSize};
font-weight: 600;
letter-spacing: ${ATTRIBUTION_LAYOUT.letterSpacing};
color: ${ATTRIBUTION_BADGE_COLORS.text};
white-space: nowrap;
}

.${ATTRIBUTION_LOGO_CLASS} {
display: flex;
align-items: center;
justify-content: center;
width: ${ATTRIBUTION_LAYOUT.logoHeight};
height: calc(${ATTRIBUTION_LAYOUT.logoHeight} * ${ATTRIBUTION_LOGO_VIEWBOX.width} / ${ATTRIBUTION_LOGO_VIEWBOX.height});
flex-shrink: 0;
}

.${ATTRIBUTION_LOGO_CLASS} svg {
height: ${ATTRIBUTION_LAYOUT.logoHeight};
width: auto;
transform: rotate(-90deg);
display: block;
}
`;
49 changes: 49 additions & 0 deletions packages/browser/src/branding/resolveShowAttribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* 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.
*/

/**
* Resolves whether the "Powered by ThunderID" attribution badge should be shown, given the
* root-level (`ThunderIDProvider`'s `showAttribution` config field) and an optional
* component-level `showAttribution` prop override.
*
* Precedence: the component-level value wins when defined, otherwise the root-level value is
* used, otherwise the badge defaults to shown.
*
* Shared across framework SDKs so every wrapper (`@thunderid/react`, `@thunderid/vue`, ...)
* applies the exact same precedence instead of re-implementing it.
*
* @param rootShowAttribution - `showAttribution` supplied to the root provider (e.g. `ThunderIDProvider`).
* @param componentShowAttribution - Optional component-level `showAttribution` prop override.
* @returns Whether the attribution badge should render.
*/
const resolveShowAttribution = (
rootShowAttribution?: boolean,
componentShowAttribution?: boolean,
): boolean => {
if (componentShowAttribution !== undefined) {
return componentShowAttribution;
}

if (rootShowAttribution !== undefined) {
return rootShowAttribution;
}

return true;
};

export default resolveShowAttribution;
15 changes: 15 additions & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ export {detectThemeMode, createClassObserver, createMediaQueryListener} from './
export type {BrowserThemeDetection} from './theme/themeDetection';
export {default as getActiveTheme} from './theme/getActiveTheme';
export {default as normalizeBorderRadius} from './theme/normalizeBorderRadius';
export {getElevatedSurfaceTokens} from './theme/elevatedSurface';
export type {ElevatedSurfaceTokens} from './theme/elevatedSurface';

// Branding — shared design system tokens/logic for framework SDKs (react, vue, ...)
export {
ATTRIBUTION_BADGE_COLORS,
ATTRIBUTION_BLOCK,
ATTRIBUTION_CONTENT_CLASS,
ATTRIBUTION_LABEL_CLASS,
ATTRIBUTION_LAYOUT,
ATTRIBUTION_LOGO_CLASS,
ATTRIBUTION_LOGO_SVG_MARKUP,
buildAttributionCss,
} from './branding/attributionDesign';
export {default as resolveShowAttribution} from './branding/resolveShowAttribution';

// Re-export everything from the JavaScript SDK
export * from '@thunderid/javascript';
66 changes: 66 additions & 0 deletions packages/browser/src/theme/elevatedSurface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* 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 {Theme} from '@thunderid/javascript';

export interface ElevatedSurfaceTokens {
/**
* Themed surface background.
*/
background: string;
/**
* Themed 1px border shorthand (`1px solid <color>`).
*/
border: string;
/**
* Large corner radius, shared by every top-level card surface (auth cards, dropdowns,
* profile/organization panels).
*/
borderRadius: string;
/**
* Elevated drop shadow used to lift a card off the page background.
*/
boxShadow: string;
/**
* Comfortable card padding (vertical, horizontal).
*/
padding: string;
}

/**
* Resolves the shared "elevated surface" look — the rounded, bordered, drop-shadowed card
* treatment used across every top-level ThunderID surface (`SignIn`, `SignUp`, `Recovery`,
* `UserDropdown`, `UserProfile`, and friends).
*
* Centralizing this recipe here means every framework SDK composes the exact same combination
* of theme tokens instead of each component picking its own subset (which is how, prior to
* this, some cards had a shadow and others didn't). Consumers spread the result into their own
* CSS-in-JS or stylesheet rules alongside layout-specific properties (`gap`, `min-width`, ...).
*
* @param theme - The resolved theme object.
* @returns The shared elevated-surface CSS token values.
*/
export const getElevatedSurfaceTokens = (theme: Theme): ElevatedSurfaceTokens => ({
background: theme.vars.colors.background.surface,
border: `1px solid ${theme.vars.colors.border}`,
borderRadius: theme.vars.borderRadius.large,
boxShadow: theme.vars.shadows.large,
padding: `calc(${theme.vars.spacing.unit} * 4) calc(${theme.vars.spacing.unit} * 3.5)`,
});

export default getElevatedSurfaceTokens;
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/models/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface I18nTranslations {
'elements.display.divider.or_separator': string;
'elements.display.copyable_text.copy': string;
'elements.display.copyable_text.copied': string;
'elements.display.attribution.powered_by': string;

/* Fields */
'elements.fields.generic.placeholder': string;
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'OR',
'elements.display.copyable_text.copy': 'Copy',
'elements.display.copyable_text.copied': 'Copied!',
'elements.display.attribution.powered_by': 'Powered by',

/* Fields */
'elements.fields.generic.placeholder': 'Enter your {field}',
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/fr-FR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'OU',
'elements.display.copyable_text.copy': 'Copie',
'elements.display.copyable_text.copied': 'Copié!',
'elements.display.attribution.powered_by': 'Propulsé par',

/* Fields */
'elements.fields.generic.placeholder': 'Entrez votre {field}',
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/hi-IN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'या',
'elements.display.copyable_text.copy': 'प्रतिलिपि',
'elements.display.copyable_text.copied': 'नकल की गई!',
'elements.display.attribution.powered_by': 'द्वारा संचालित',

/* Fields */
'elements.fields.generic.placeholder': '{field} दर्ज करें',
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'または',
'elements.display.copyable_text.copy': 'コピー',
'elements.display.copyable_text.copied': 'コピーしました!',
'elements.display.attribution.powered_by': '提供',

/* Fields */
'elements.fields.generic.placeholder': '{field}を入力してください',
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'OU',
'elements.display.copyable_text.copy': 'Cópia',
'elements.display.copyable_text.copied': 'Copiado!',
'elements.display.attribution.powered_by': 'Desenvolvido por',

/* Fields */
'elements.fields.generic.placeholder': 'Digite seu {field}',
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/pt-PT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'OU',
'elements.display.copyable_text.copy': 'Cópia',
'elements.display.copyable_text.copied': 'Copiado!',
'elements.display.attribution.powered_by': 'Desenvolvido por',

/* Fields */
'elements.fields.generic.placeholder': 'Introduza o seu {field}',
Expand Down
1 change: 1 addition & 0 deletions packages/javascript/src/i18n/translations/si-LK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const translations: I18nTranslations = {
'elements.display.divider.or_separator': 'හෝ',
'elements.display.copyable_text.copy': 'පිටපත් කරන්න',
'elements.display.copyable_text.copied': 'පිටපත් කළා!',
'elements.display.attribution.powered_by': 'මගින් බලගැන්වේ',

/* Fields */
'elements.fields.generic.placeholder': 'ඔබේ {field} ඇතුලත් කරන්න',
Expand Down
Loading
Loading