diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index b4e67fe..0468d0a 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -59,6 +59,7 @@ export {default as resolveEmojiUrisInHtml} from './utils/resolveEmojiUrisInHtml' export {detectThemeMode, createClassObserver, createMediaQueryListener} from './theme/themeDetection'; export type {BrowserThemeDetection} from './theme/themeDetection'; export {default as getActiveTheme} from './theme/getActiveTheme'; +export {default as normalizeBorderRadius} from './theme/normalizeBorderRadius'; // Re-export everything from the JavaScript SDK export * from '@thunderid/javascript'; diff --git a/packages/browser/src/theme/normalizeBorderRadius.ts b/packages/browser/src/theme/normalizeBorderRadius.ts new file mode 100644 index 0000000..2e54f39 --- /dev/null +++ b/packages/browser/src/theme/normalizeBorderRadius.ts @@ -0,0 +1,58 @@ +/** + * 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 {FlowMetaThemeShape} from '@thunderid/javascript'; + +/** + * Size configuration in the v2 theme. + * + * @experimental This API may change in future versions + */ +interface FlowMetaThemeSize { + small?: string; + medium?: string; + large?: string; +} +/** + * Normalizes a raw `shape.borderRadius` value into per-size tokens suitable + * for `ThemeConfig.borderRadius`. + * + * - Object with `small`/`medium`/`large` → used directly. + * - Bare number or numeric string (e.g. `8` or `"8"`) → appended with `px` and applied uniformly. + * - Pixel string (e.g. `"8px"`) → used as-is and applied uniformly. + */ +const normalizeBorderRadius = (raw: FlowMetaThemeShape['borderRadius']): FlowMetaThemeSize | undefined => { + if (raw === undefined) return undefined; + + if (typeof raw === 'object') { + const {small, medium, large}: FlowMetaThemeSize = raw; + if (small === undefined && medium === undefined && large === undefined) return undefined; + return { + ...(large !== undefined && {large}), + ...(medium !== undefined && {medium}), + ...(small !== undefined && {small}), + }; + } + + const trimmed: string = String(raw).trim(); + const radiusStr: string = + typeof raw === 'number' ? `${raw}px` : /^\d+(\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed; + return {large: radiusStr, medium: radiusStr, small: radiusStr}; +}; + +export default normalizeBorderRadius; diff --git a/packages/javascript/src/models/flow-meta.ts b/packages/javascript/src/models/flow-meta.ts index f2b5272..b1c6206 100644 --- a/packages/javascript/src/models/flow-meta.ts +++ b/packages/javascript/src/models/flow-meta.ts @@ -168,14 +168,25 @@ export interface FlowMetaThemeColorScheme { palette: FlowMetaThemeColors; } +/** + * Size configuration in the v2 theme. + * + * @experimental This API may change in future versions + */ +export interface FlowMetaThemeSize { + small?: string; + medium?: string; + large?: string; +} + /** * Shape / geometry configuration in the v2 theme. * * @experimental This API may change in future versions */ export interface FlowMetaThemeShape { - /** Border radius in pixels applied uniformly across components */ - borderRadius?: number; + /** Border radius: a number (px), a numeric string ("8"), a px string ("8px"), or per-size tokens */ + borderRadius?: number | string | FlowMetaThemeSize; } /** @@ -215,6 +226,15 @@ export interface FlowMetaTheme { /** Typography configuration for the theme */ typography?: FlowMetaThemeTypography; + + /** Border configuration for the theme */ + border?: {width: string; color: string; style: string}; + + /** Shadow configuration for the theme */ + shadows?: FlowMetaThemeSize; + + /** Spacing configuration for the theme */ + spacing?: {unit: number}; } /** diff --git a/packages/react/src/utils/buildThemeConfigFromFlowMeta.ts b/packages/react/src/utils/buildThemeConfigFromFlowMeta.ts index 9ad977c..f69a49b 100644 --- a/packages/react/src/utils/buildThemeConfigFromFlowMeta.ts +++ b/packages/react/src/utils/buildThemeConfigFromFlowMeta.ts @@ -16,7 +16,13 @@ * under the License. */ -import {FlowMetaTheme, FlowMetaThemeColorScheme, RecursivePartial, ThemeConfig} from '@thunderid/browser'; +import { + FlowMetaTheme, + FlowMetaThemeColorScheme, + RecursivePartial, + ThemeConfig, + normalizeBorderRadius, +} from '@thunderid/browser'; /** * Converts a v2 `FlowMetaTheme` into a `RecursivePartial` that @@ -36,8 +42,7 @@ const buildThemeConfigFromFlowMeta = ( colorScheme: 'light' | 'dark', ): RecursivePartial => { const scheme: FlowMetaThemeColorScheme | undefined = flowMetaTheme.colorSchemes?.[colorScheme]; - const borderRadius: number | undefined = flowMetaTheme.shape?.borderRadius; - const borderRadiusStr: string | undefined = borderRadius !== undefined ? `${borderRadius}px` : undefined; + const borderRadiusConfig = normalizeBorderRadius(flowMetaTheme.shape?.borderRadius); // Build only the colors that the server actually provided. // Each nested object is constructed incrementally so that absent fields are @@ -76,16 +81,9 @@ const buildThemeConfigFromFlowMeta = ( } return { + ...flowMetaTheme, ...(flowMetaTheme.direction ? {direction: flowMetaTheme.direction} : {}), - ...(borderRadiusStr - ? { - borderRadius: { - large: borderRadiusStr, - medium: borderRadiusStr, - small: borderRadiusStr, - }, - } - : {}), + ...(borderRadiusConfig ? {borderRadius: borderRadiusConfig} : {}), ...(colors && Object.keys(colors).length > 0 ? {colors} : {}), ...(flowMetaTheme.typography?.fontFamily ? {typography: {fontFamily: flowMetaTheme.typography.fontFamily}} : {}), }; diff --git a/packages/vue/src/utils/buildThemeConfigFromFlowMeta.ts b/packages/vue/src/utils/buildThemeConfigFromFlowMeta.ts index cb4ff47..9ad61d5 100644 --- a/packages/vue/src/utils/buildThemeConfigFromFlowMeta.ts +++ b/packages/vue/src/utils/buildThemeConfigFromFlowMeta.ts @@ -16,7 +16,13 @@ * under the License. */ -import {FlowMetaTheme, FlowMetaThemeColorScheme, RecursivePartial, ThemeConfig} from '@thunderid/browser'; +import { + FlowMetaTheme, + FlowMetaThemeColorScheme, + RecursivePartial, + ThemeConfig, + normalizeBorderRadius, +} from '@thunderid/browser'; /** * Converts a v2 `FlowMetaTheme` into a `RecursivePartial` that @@ -31,8 +37,7 @@ const buildThemeConfigFromFlowMeta = ( colorScheme: 'light' | 'dark', ): RecursivePartial => { const scheme: FlowMetaThemeColorScheme | undefined = flowMetaTheme.colorSchemes?.[colorScheme]; - const borderRadius: number | undefined = flowMetaTheme.shape?.borderRadius; - const borderRadiusStr: string | undefined = borderRadius !== undefined ? `${borderRadius}px` : undefined; + const borderRadiusConfig = normalizeBorderRadius(flowMetaTheme.shape?.borderRadius); let colors: RecursivePartial | undefined; @@ -66,16 +71,9 @@ const buildThemeConfigFromFlowMeta = ( } return { + ...flowMetaTheme, ...(flowMetaTheme.direction ? {direction: flowMetaTheme.direction} : {}), - ...(borderRadiusStr - ? { - borderRadius: { - large: borderRadiusStr, - medium: borderRadiusStr, - small: borderRadiusStr, - }, - } - : {}), + ...(borderRadiusConfig ? {borderRadius: borderRadiusConfig} : {}), ...(colors && Object.keys(colors).length > 0 ? {colors} : {}), ...(flowMetaTheme.typography?.fontFamily ? {typography: {fontFamily: flowMetaTheme.typography.fontFamily}} : {}), };