diff --git a/.changeset/sweet-geese-guess.md b/.changeset/sweet-geese-guess.md new file mode 100644 index 00000000..6b98ab2b --- /dev/null +++ b/.changeset/sweet-geese-guess.md @@ -0,0 +1,6 @@ +--- +'@asgardeo/javascript': patch +'@asgardeo/react': patch +--- + +Add component-specific overrides. diff --git a/packages/javascript/src/theme/createTheme.ts b/packages/javascript/src/theme/createTheme.ts index 323a3554..6a80e030 100644 --- a/packages/javascript/src/theme/createTheme.ts +++ b/packages/javascript/src/theme/createTheme.ts @@ -449,12 +449,43 @@ const toCssVariables = (theme: ThemeConfig): Record => { }); } + /* |---------------------------------------------------------------| */ + /* | Components | */ + /* |---------------------------------------------------------------| */ + + // Button Overrides + if (theme.components?.Button?.styleOverrides?.root?.borderRadius) { + cssVars[`--${prefix}-component-button-root-borderRadius`] = + theme.components.Button.styleOverrides.root.borderRadius; + } + + // Field Overrides (Parent of `TextField`, `DatePicker`, `OtpField`, `Select`, etc.) + if (theme.components?.Field?.styleOverrides?.root?.borderRadius) { + cssVars[`--${prefix}-component-field-root-borderRadius`] = theme.components.Field.styleOverrides.root.borderRadius; + } + return cssVars; }; const toThemeVars = (theme: ThemeConfig): ThemeVars => { const prefix = theme.cssVarPrefix || VendorConstants.VENDOR_PREFIX; + const componentVars: ThemeVars['components'] = {}; + if (theme.components?.Button?.styleOverrides?.root?.borderRadius) { + componentVars.Button = { + root: { + borderRadius: `var(--${prefix}-component-button-root-borderRadius)`, + }, + }; + } + if (theme.components?.Field?.styleOverrides?.root?.borderRadius) { + componentVars.Field = { + root: { + borderRadius: `var(--${prefix}-component-field-root-borderRadius)`, + }, + }; + } + const themeVars: ThemeVars = { colors: { action: { @@ -558,6 +589,10 @@ const toThemeVars = (theme: ThemeConfig): ThemeVars => { }); } + if (Object.keys(componentVars).length > 0) { + themeVars.components = componentVars; + } + return themeVars; }; diff --git a/packages/javascript/src/theme/types.ts b/packages/javascript/src/theme/types.ts index 19aea3a2..1dd5ae7e 100644 --- a/packages/javascript/src/theme/types.ts +++ b/packages/javascript/src/theme/types.ts @@ -101,6 +101,47 @@ export interface ThemeColors { }; } +export interface ThemeComponentStyleOverrides { + /** + * Style overrides for the root element or slots. + * Example: { root: { borderRadius: '8px' } } + */ + root?: Record; + [slot: string]: Record | undefined; +} + +export interface ThemeComponents { + Button?: { + styleOverrides?: { + root?: { + borderRadius?: string; + [key: string]: any; + }; + [slot: string]: Record | undefined; + }; + defaultProps?: Record; + variants?: Array>; + }; + Field?: { + styleOverrides?: { + root?: { + borderRadius?: string; + [key: string]: any; + }; + [slot: string]: Record | undefined; + }; + defaultProps?: Record; + variants?: Array>; + }; + [componentName: string]: + | { + styleOverrides?: ThemeComponentStyleOverrides; + defaultProps?: Record; + variants?: Array>; + } + | undefined; +} + export interface ThemeConfig { borderRadius: { large: string; @@ -148,6 +189,32 @@ export interface ThemeConfig { * @default 'asgardeo' (from VendorConstants.VENDOR_PREFIX) */ cssVarPrefix?: string; + /** + * Component style overrides + */ + components?: ThemeComponents; +} + +export interface ThemeComponentVars { + Button?: { + root?: { + borderRadius?: string; + [key: string]: any; + }; + [slot: string]: Record | undefined; + }; + Field?: { + root?: { + borderRadius?: string; + [key: string]: any; + }; + [slot: string]: Record | undefined; + }; + [componentName: string]: + | { + [slot: string]: Record | undefined; + } + | undefined; } export interface ThemeVars { @@ -266,6 +333,10 @@ export interface ThemeVars { } | undefined; }; + /** + * Component CSS variable references (e.g., for overrides) + */ + components?: ThemeComponentVars; } export interface Theme extends ThemeConfig { diff --git a/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts b/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts index 494c1a23..d11e709b 100644 --- a/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts +++ b/packages/javascript/src/utils/transformBrandingPreferenceToTheme.ts @@ -49,7 +49,7 @@ const transformThemeVariant = (themeVariant: ThemeVariant, isDark = false): Part const inputs = themeVariant.inputs; const images = themeVariant.images; - return { + const config: Partial = { colors: { action: { active: isDark ? 'rgba(255, 255, 255, 0.70)' : 'rgba(0, 0, 0, 0.54)', @@ -111,13 +111,6 @@ const transformThemeVariant = (themeVariant: ThemeVariant, isDark = false): Part dark: (colors?.alerts?.warning as ColorVariant)?.dark || (colors?.alerts?.warning as ColorVariant)?.main, }, }, - // Extract border radius from buttons or inputs - borderRadius: { - small: buttons?.primary?.base?.border?.borderRadius || inputs?.base?.border?.borderRadius, - medium: buttons?.secondary?.base?.border?.borderRadius, - large: buttons?.externalConnection?.base?.border?.borderRadius, - }, - // Extract and transform images images: { favicon: images?.favicon ? { @@ -135,6 +128,38 @@ const transformThemeVariant = (themeVariant: ThemeVariant, isDark = false): Part : undefined, }, }; + + /* |---------------------------------------------------------------| */ + /* | Components | */ + /* |---------------------------------------------------------------| */ + + const buttonBorderRadius = buttons?.primary?.base?.border?.borderRadius; + const fieldBorderRadius = inputs?.base?.border?.borderRadius; + + if (buttonBorderRadius || fieldBorderRadius) { + config.components = { + ...(buttonBorderRadius && { + Button: { + styleOverrides: { + root: { + borderRadius: buttonBorderRadius, + }, + }, + }, + }), + ...(fieldBorderRadius && { + Field: { + styleOverrides: { + root: { + borderRadius: fieldBorderRadius, + }, + }, + }, + }), + }; + } + + return config; }; /** diff --git a/packages/react/src/components/primitives/Button/Button.styles.ts b/packages/react/src/components/primitives/Button/Button.styles.ts index 025bf466..282d420d 100644 --- a/packages/react/src/components/primitives/Button/Button.styles.ts +++ b/packages/react/src/components/primitives/Button/Button.styles.ts @@ -53,7 +53,9 @@ const useStyles = ( align-items: center; justify-content: center; gap: calc(${theme.vars.spacing.unit} * 1); - border-radius: ${shape === 'round' ? '50%' : theme.vars.borderRadius.medium}; + border-radius: ${shape === 'round' + ? '50%' + : theme.vars.components?.Button?.root?.borderRadius || theme.vars.borderRadius.medium}; font-weight: 500; cursor: ${disabled || loading ? 'not-allowed' : 'pointer'}; outline: none; diff --git a/packages/react/src/components/primitives/DatePicker/DatePicker.styles.ts b/packages/react/src/components/primitives/DatePicker/DatePicker.styles.ts index 2adbdecf..214e51de 100644 --- a/packages/react/src/components/primitives/DatePicker/DatePicker.styles.ts +++ b/packages/react/src/components/primitives/DatePicker/DatePicker.styles.ts @@ -34,7 +34,7 @@ const useStyles = (theme: Theme, colorScheme: string, hasError: boolean, disable width: 100%; padding: ${theme.vars.spacing.unit} calc(${theme.vars.spacing.unit} * 1.5); border: 1px solid ${theme.vars.colors.border}; - border-radius: ${theme.vars.borderRadius.medium}; + border-radius: ${theme.vars.components?.Field?.root?.borderRadius || theme.vars.borderRadius.medium}; font-size: 1rem; color: ${theme.vars.colors.text.primary}; background-color: ${theme.vars.colors.background.surface}; diff --git a/packages/react/src/components/primitives/OtpField/OtpField.styles.ts b/packages/react/src/components/primitives/OtpField/OtpField.styles.ts index 011c5b1e..c43f4309 100644 --- a/packages/react/src/components/primitives/OtpField/OtpField.styles.ts +++ b/packages/react/src/components/primitives/OtpField/OtpField.styles.ts @@ -54,7 +54,7 @@ const useStyles = ( font-size: ${theme.vars.typography.fontSizes.xl}; font-weight: 500; border: 2px solid ${hasError ? theme.vars.colors.error.main : theme.vars.colors.border}; - border-radius: ${theme.vars.borderRadius.medium}; + border-radius: ${theme.vars.components?.Field?.root?.borderRadius || theme.vars.borderRadius.medium}; color: ${theme.vars.colors.text.primary}; background-color: ${disabled ? theme.vars.colors.background.disabled : theme.vars.colors.background.surface}; outline: none; diff --git a/packages/react/src/components/primitives/Select/Select.styles.ts b/packages/react/src/components/primitives/Select/Select.styles.ts index 6907f5cb..0a83576e 100644 --- a/packages/react/src/components/primitives/Select/Select.styles.ts +++ b/packages/react/src/components/primitives/Select/Select.styles.ts @@ -37,7 +37,7 @@ const useStyles = (theme: Theme, colorScheme: string, disabled: boolean, hasErro width: 100%; padding: ${theme.vars.spacing.unit} calc(${theme.vars.spacing.unit} * 1.5); border: 1px solid ${hasError ? theme.vars.colors.error.main : theme.vars.colors.border}; - border-radius: ${theme.vars.borderRadius.medium}; + border-radius: ${theme.vars.components?.Field?.root?.borderRadius || theme.vars.borderRadius.medium}; font-size: ${theme.vars.typography.fontSizes.md}; color: ${theme.vars.colors.text.primary}; background-color: ${disabled ? theme.vars.colors.background.disabled : theme.vars.colors.background.surface}; diff --git a/packages/react/src/components/primitives/TextField/TextField.styles.ts b/packages/react/src/components/primitives/TextField/TextField.styles.ts index 14b02e67..99225e0b 100644 --- a/packages/react/src/components/primitives/TextField/TextField.styles.ts +++ b/packages/react/src/components/primitives/TextField/TextField.styles.ts @@ -54,7 +54,7 @@ const useStyles = ( width: 100%; padding: ${theme.vars.spacing.unit} ${rightPadding} ${theme.vars.spacing.unit} ${leftPadding}; border: 1px solid ${hasError ? theme.vars.colors.error.main : theme.vars.colors.border}; - border-radius: ${theme.vars.borderRadius.medium}; + border-radius: ${theme.vars.components?.Field?.root?.borderRadius || theme.vars.borderRadius.medium}; font-size: ${theme.vars.typography.fontSizes.md}; color: ${theme.vars.colors.text.primary}; background-color: ${disabled ? theme.vars.colors.background.disabled : theme.vars.colors.background.surface}; diff --git a/packages/react/src/contexts/Theme/ThemeProvider.tsx b/packages/react/src/contexts/Theme/ThemeProvider.tsx index 204801a8..8494dcc1 100644 --- a/packages/react/src/contexts/Theme/ThemeProvider.tsx +++ b/packages/react/src/contexts/Theme/ThemeProvider.tsx @@ -175,6 +175,7 @@ const ThemeProvider: FC> = ({ shadows: brandingTheme.shadows, spacing: brandingTheme.spacing, images: brandingTheme.images, + components: brandingTheme.components, }; // Merge branding theme with user-provided theme config @@ -202,6 +203,10 @@ const ThemeProvider: FC> = ({ ...brandingThemeConfig.images, ...themeConfig?.images, }, + components: { + ...brandingThemeConfig.components, + ...themeConfig?.components, + }, }; }, [inheritFromBranding, brandingTheme, themeConfig]);