))
) : (
pairs.map((pair, index) => (
-
+
handleUpdatePair(index, 'key', e.target.value)}
disabled={disabled || readOnly}
- style={styles.pairInput}
+ className={cx(withVendorCSSClassPrefix(bem('key-value-input', 'pair-input')), styles.pairInput)}
aria-label={`${keyLabel} ${index + 1}`}
/>
= ({
value={pair.value}
onChange={e => handleUpdatePair(index, 'value', e.target.value)}
disabled={disabled || readOnly}
- style={styles.pairInput}
+ className={cx(withVendorCSSClassPrefix(bem('key-value-input', 'pair-input')), styles.pairInput)}
aria-label={`${valueLabel} ${index + 1}`}
/>
{!readOnly && (
-
+
)}
))
)}
{!readOnly && (
-
+
setNewKey(e.target.value)}
disabled={disabled}
- style={styles.pairInput}
+ className={cx(withVendorCSSClassPrefix(bem('key-value-input', 'pair-input')), styles.pairInput)}
aria-label="New key"
/>
= ({
value={newValue}
onChange={e => setNewValue(e.target.value)}
disabled={disabled}
- style={styles.pairInput}
+ className={cx(withVendorCSSClassPrefix(bem('key-value-input', 'pair-input')), styles.pairInput)}
aria-label="New value"
onKeyPress={e => {
if (e.key === 'Enter' && !isAddDisabled) {
@@ -422,24 +334,27 @@ const KeyValueInput: FC = ({
}
}}
/>
-
+
)}
- {(helperText || error) &&
{error || helperText}
}
+ {(helperText || error) && (
+
+ {error || helperText}
+
+ )}
{maxPairs && (
-
+
{pairs.length} of {maxPairs} pairs used
)}
diff --git a/packages/react/src/components/primitives/KeyValueInput/index.ts b/packages/react/src/components/primitives/KeyValueInput/index.ts
deleted file mode 100644
index e69de29b..00000000
From 24a2adc45ea6615fdf6c4c7cc70ab57cbc3d72c4 Mon Sep 17 00:00:00 2001
From: Brion
Date: Wed, 9 Jul 2025 09:27:44 +0530
Subject: [PATCH 27/50] chore(react): implement Logo component styles using BEM
methodology and refactor Logo to utilize new styles
---
.../components/primitives/Logo/Logo.styles.ts | 71 +++++++++++++++++++
.../src/components/primitives/Logo/Logo.tsx | 55 ++++++--------
2 files changed, 93 insertions(+), 33 deletions(-)
create mode 100644 packages/react/src/components/primitives/Logo/Logo.styles.ts
diff --git a/packages/react/src/components/primitives/Logo/Logo.styles.ts b/packages/react/src/components/primitives/Logo/Logo.styles.ts
new file mode 100644
index 00000000..014f44a9
--- /dev/null
+++ b/packages/react/src/components/primitives/Logo/Logo.styles.ts
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2025, 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 {css} from '@emotion/css';
+import {useMemo} from 'react';
+import {Theme} from '@asgardeo/browser';
+
+export type LogoSize = 'small' | 'medium' | 'large';
+
+/**
+ * Creates styles for the Logo component using BEM methodology
+ * @param theme - The theme object containing design tokens
+ * @param colorScheme - The current color scheme (used for memoization)
+ * @param size - The size of the logo
+ * @returns Object containing CSS class names for component styling
+ */
+const useStyles = (theme: Theme, colorScheme: string, size: LogoSize) => {
+ return useMemo(() => {
+ const baseLogo = css`
+ width: auto;
+ object-fit: contain;
+ display: block;
+ `;
+
+ const smallLogo = css`
+ height: 32px;
+ max-width: 120px;
+ `;
+
+ const mediumLogo = css`
+ height: 48px;
+ max-width: 180px;
+ `;
+
+ const largeLogo = css`
+ height: 64px;
+ max-width: 240px;
+ `;
+
+ const sizeStyles = {
+ small: smallLogo,
+ medium: mediumLogo,
+ large: largeLogo,
+ };
+
+ return {
+ logo: baseLogo,
+ size: sizeStyles[size],
+ small: smallLogo,
+ medium: mediumLogo,
+ large: largeLogo,
+ };
+ }, [theme, colorScheme, size]);
+};
+
+export default useStyles;
diff --git a/packages/react/src/components/primitives/Logo/Logo.tsx b/packages/react/src/components/primitives/Logo/Logo.tsx
index e82d8ac4..a9563e61 100644
--- a/packages/react/src/components/primitives/Logo/Logo.tsx
+++ b/packages/react/src/components/primitives/Logo/Logo.tsx
@@ -18,8 +18,11 @@
import {FC} from 'react';
import {cx} from '@emotion/css';
-import {withVendorCSSClassPrefix} from '@asgardeo/browser';
+import {withVendorCSSClassPrefix, bem} from '@asgardeo/browser';
import useTheme from '../../../contexts/Theme/useTheme';
+import useStyles from './Logo.styles';
+
+export type LogoSize = 'small' | 'medium' | 'large';
/**
* Props for the Logo component.
@@ -44,11 +47,7 @@ export interface LogoProps {
/**
* Size of the logo.
*/
- size?: 'small' | 'medium' | 'large';
- /**
- * Custom style object.
- */
- style?: React.CSSProperties;
+ size?: LogoSize;
}
/**
@@ -57,8 +56,9 @@ export interface LogoProps {
* @param props - The props for the Logo component.
* @returns The rendered Logo component.
*/
-const Logo: FC = ({className, src, alt, title, size = 'medium', style}) => {
- const {theme} = useTheme();
+const Logo: FC = ({className, src, alt, title, size = 'medium'}) => {
+ const {theme, colorScheme} = useTheme();
+ const styles = useStyles(theme, colorScheme, size);
// Get logo configuration from theme - use actual values, not CSS variables
// Access the actual theme config values, not the CSS variable references from .vars
@@ -70,35 +70,24 @@ const Logo: FC = ({className, src, alt, title, size = 'medium', style
const logoTitle = title || logoConfig?.title;
- const logoClasses = cx(withVendorCSSClassPrefix('logo'), withVendorCSSClassPrefix(`logo--${size}`), className);
-
- const sizeStyles: Record = {
- small: {
- height: '32px',
- maxWidth: '120px',
- },
- medium: {
- height: '48px',
- maxWidth: '180px',
- },
- large: {
- height: '64px',
- maxWidth: '240px',
- },
- };
-
- const defaultStyles: React.CSSProperties = {
- width: 'auto',
- objectFit: 'contain',
- ...sizeStyles[size],
- ...style,
- };
-
if (!logoSrc) {
return null;
}
- return
;
+ return (
+
+ );
};
export default Logo;
From b82d1e2d1a4d67e63782d4def0abed4a91fc8749 Mon Sep 17 00:00:00 2001
From: Brion
Date: Wed, 9 Jul 2025 10:09:41 +0530
Subject: [PATCH 28/50] chore(react): add MultiInput component styles using BEM
methodology and refactor MultiInput to utilize new styles
---
.../MultiInput/MultiInput.styles.ts | 141 ++++++++++++++++++
.../primitives/MultiInput/MultiInput.tsx | 139 +++++------------
2 files changed, 177 insertions(+), 103 deletions(-)
create mode 100644 packages/react/src/components/primitives/MultiInput/MultiInput.styles.ts
diff --git a/packages/react/src/components/primitives/MultiInput/MultiInput.styles.ts b/packages/react/src/components/primitives/MultiInput/MultiInput.styles.ts
new file mode 100644
index 00000000..ab84e1f7
--- /dev/null
+++ b/packages/react/src/components/primitives/MultiInput/MultiInput.styles.ts
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2025, 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 {css} from '@emotion/css';
+import {useMemo} from 'react';
+import {Theme} from '@asgardeo/browser';
+
+export type MultiInputType = 'text' | 'email' | 'tel' | 'url' | 'password' | 'date' | 'boolean';
+export type MultiInputFieldType = 'STRING' | 'DATE_TIME' | 'BOOLEAN';
+
+/**
+ * Creates styles for the MultiInput component using BEM methodology
+ * @param theme - The theme object containing design tokens
+ * @param colorScheme - The current color scheme (used for memoization)
+ * @param disabled - Whether the component is disabled
+ * @param hasError - Whether the component has an error
+ * @param canAddMore - Whether more items can be added
+ * @param canRemove - Whether items can be removed
+ * @returns Object containing CSS class names for component styling
+ */
+const useStyles = (
+ theme: Theme,
+ colorScheme: string,
+ disabled: boolean,
+ hasError: boolean,
+ canAddMore: boolean,
+ canRemove: boolean,
+) => {
+ return useMemo(() => {
+ const container = css`
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.vars.spacing.unit};
+ `;
+
+ const inputRow = css`
+ display: flex;
+ align-items: center;
+ gap: ${theme.vars.spacing.unit};
+ position: relative;
+ `;
+
+ const inputWrapper = css`
+ flex: 1;
+ `;
+
+ const plusIcon = css`
+ background: ${theme.vars.colors.secondary.main};
+ border-radius: 50%;
+ outline: 4px ${theme.vars.colors.secondary.main} auto;
+ color: ${theme.vars.colors.secondary.contrastText};
+ `;
+
+ const listContainer = css`
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ `;
+
+ const listItem = css`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: ${theme.vars.spacing.unit} calc(${theme.vars.spacing.unit} * 1.5);
+ background-color: ${theme.vars.colors.background.surface};
+ border-radius: ${theme.vars.borderRadius.medium};
+ font-size: 1rem;
+ color: ${theme.vars.colors.text.primary};
+ margin-bottom: calc(${theme.vars.spacing.unit} / 2);
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ `;
+
+ const listItemText = css`
+ flex: 1;
+ word-break: break-word;
+ `;
+
+ const removeButton = css`
+ padding: calc(${theme.vars.spacing.unit} / 2);
+ min-width: auto;
+ color: ${theme.vars.colors.error.main};
+ background: transparent;
+ border: none;
+ border-radius: ${theme.vars.borderRadius.small};
+ cursor: ${disabled ? 'not-allowed' : 'pointer'};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover:not(:disabled) {
+ background-color: ${theme.vars.colors.action.hover};
+ }
+
+ &:disabled {
+ opacity: 0.6;
+ }
+ `;
+
+ const icon = css`
+ width: 16px;
+ height: 16px;
+ stroke: currentColor;
+ stroke-width: 2;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ fill: none;
+ `;
+
+ return {
+ container,
+ inputRow,
+ inputWrapper,
+ plusIcon,
+ listContainer,
+ listItem,
+ listItemText,
+ removeButton,
+ icon,
+ };
+ }, [theme, colorScheme, disabled, hasError, canAddMore, canRemove]);
+};
+
+export default useStyles;
diff --git a/packages/react/src/components/primitives/MultiInput/MultiInput.tsx b/packages/react/src/components/primitives/MultiInput/MultiInput.tsx
index 03149edf..ab46fc36 100644
--- a/packages/react/src/components/primitives/MultiInput/MultiInput.tsx
+++ b/packages/react/src/components/primitives/MultiInput/MultiInput.tsx
@@ -16,7 +16,7 @@
* under the License.
*/
-import {CSSProperties, FC, ReactNode, useCallback, useState, useMemo} from 'react';
+import {FC, ReactNode, useCallback, useState} from 'react';
import useTheme from '../../../contexts/Theme/useTheme';
import {cx} from '@emotion/css';
import FormControl from '../FormControl/FormControl';
@@ -24,8 +24,11 @@ import InputLabel from '../InputLabel/InputLabel';
import TextField from '../TextField/TextField';
import DatePicker from '../DatePicker/DatePicker';
import Checkbox from '../Checkbox/Checkbox';
-import Button from '../Button/Button';
-import {withVendorCSSClassPrefix} from '@asgardeo/browser';
+import {withVendorCSSClassPrefix, bem} from '@asgardeo/browser';
+import useStyles from './MultiInput.styles';
+
+export type MultiInputType = 'text' | 'email' | 'tel' | 'url' | 'password' | 'date' | 'boolean';
+export type MultiInputFieldType = 'STRING' | 'DATE_TIME' | 'BOOLEAN';
export interface MultiInputProps {
/**
@@ -64,18 +67,14 @@ export interface MultiInputProps {
* Callback when values change
*/
onChange: (values: string[]) => void;
- /**
- * Custom style object
- */
- style?: CSSProperties;
/**
* Input type
*/
- type?: 'text' | 'email' | 'tel' | 'url' | 'password' | 'date' | 'boolean';
+ type?: MultiInputType;
/**
* Field type for different input components
*/
- fieldType?: 'STRING' | 'DATE_TIME' | 'BOOLEAN';
+ fieldType?: MultiInputFieldType;
/**
* Icon to display at the start (left) of each input
*/
@@ -94,56 +93,6 @@ export interface MultiInputProps {
maxFields?: number;
}
-const useStyles = () => {
- const {theme} = useTheme();
-
- return useMemo(
- () => ({
- container: {
- display: 'flex',
- flexDirection: 'column' as const,
- gap: `${theme.spacing.unit}px`,
- },
- inputRow: {
- display: 'flex',
- alignItems: 'center',
- gap: `${theme.spacing.unit}px`,
- position: 'relative' as const,
- },
- inputWrapper: {
- flex: 1,
- },
- plusIcon: {
- background: 'var(--asgardeo-color-secondary-main)',
- borderRadius: '50%',
- outline: '4px var(--asgardeo-color-secondary-main) auto',
- color: 'var(--asgardeo-color-secondary-contrastText)',
- },
- listContainer: {
- display: 'flex',
- flexDirection: 'column' as const,
- gap: `${theme.spacing.unit * 0}px`,
- },
- listItem: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- padding: `${theme.spacing.unit}px ${theme.spacing.unit * 1.5}px`,
- backgroundColor: theme.colors.background.surface,
- borderRadius: theme.borderRadius.medium,
- fontSize: '1rem',
- color: theme.colors.text.primary,
- },
- removeButton: {
- padding: `${theme.spacing.unit / 2}px`,
- minWidth: 'auto',
- color: theme.colors.error.main,
- },
- }),
- [theme],
- );
-};
-
const MultiInput: FC = ({
label,
error,
@@ -154,7 +103,6 @@ const MultiInput: FC = ({
placeholder = 'Enter value',
values = [],
onChange,
- style = {},
type = 'text',
fieldType = 'STRING',
startIcon,
@@ -162,35 +110,19 @@ const MultiInput: FC = ({
minFields = 1,
maxFields,
}) => {
- const styles = useStyles();
+ const {theme, colorScheme} = useTheme();
+ const canAddMore = !maxFields || values.length < maxFields;
+ const canRemove = values.length > minFields;
+ const styles = useStyles(theme, colorScheme, !!disabled, !!error, canAddMore, canRemove);
- const PlusIcon = ({style}) => (
-