Migrate PasswordField component from useStyles to useDefinition

Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
Johan Persson
2026-02-25 10:59:19 +01:00
parent a4842b4ae0
commit 59474d15b7
3 changed files with 60 additions and 60 deletions
@@ -20,15 +20,13 @@ import {
TextField as AriaTextField,
Button as RAButton,
} from 'react-aria-components';
import clsx from 'clsx';
import { FieldLabel } from '../FieldLabel';
import { FieldError } from '../FieldError';
import type { PasswordFieldProps } from './types';
import { useStyles } from '../../hooks/useStyles';
import { useDefinition } from '../../hooks/useDefinition';
import { PasswordFieldDefinition } from './definition';
import { RiEyeLine, RiEyeOffLine } from '@remixicon/react';
import stylesPasswordField from './PasswordField.module.css';
/** @public */
export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
@@ -47,24 +45,18 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
}
}, [label, ariaLabel, ariaLabelledBy]);
const { ownProps, restProps, dataAttributes } = useDefinition(
PasswordFieldDefinition,
props,
);
const {
classNames: classNamesPasswordField,
dataAttributes,
cleanedProps,
} = useStyles(PasswordFieldDefinition, {
size: 'small',
...props,
});
const {
className,
description,
classes,
icon,
isRequired,
secondaryLabel,
placeholder,
...rest
} = cleanedProps;
description,
} = ownProps;
// If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required.
const secondaryLabelText =
@@ -75,16 +67,12 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
return (
<AriaTextField
className={clsx(
classNamesPasswordField.root,
stylesPasswordField[classNamesPasswordField.root],
className,
)}
className={classes.root}
{...dataAttributes}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
type="password"
{...rest}
{...restProps}
ref={ref}
>
<FieldLabel
@@ -93,18 +81,12 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
description={description}
/>
<div
className={clsx(
classNamesPasswordField.inputWrapper,
stylesPasswordField[classNamesPasswordField.inputWrapper],
)}
className={classes.inputWrapper}
data-size={dataAttributes['data-size']}
>
{icon && (
<div
className={clsx(
classNamesPasswordField.inputIcon,
stylesPasswordField[classNamesPasswordField.inputIcon],
)}
className={classes.inputIcon}
data-size={dataAttributes['data-size']}
aria-hidden="true"
>
@@ -112,10 +94,7 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
</div>
)}
<Input
className={clsx(
classNamesPasswordField.input,
stylesPasswordField[classNamesPasswordField.input],
)}
className={classes.input}
{...(icon && { 'data-icon': true })}
placeholder={placeholder}
type={isVisible ? 'text' : 'password'}
@@ -127,10 +106,7 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
aria-controls={isVisible ? 'text' : 'password'}
aria-expanded={isVisible}
onPress={() => setIsVisible(v => !v)}
className={clsx(
classNamesPasswordField.inputVisibility,
stylesPasswordField[classNamesPasswordField.inputVisibility],
)}
className={classes.inputVisibility}
>
{isVisible ? <RiEyeLine /> : <RiEyeOffLine />}
</RAButton>
@@ -14,21 +14,32 @@
* limitations under the License.
*/
import type { ComponentDefinition } from '../../types';
import { defineComponent } from '../../hooks/useDefinition';
import type { PasswordFieldOwnProps } from './types';
import styles from './PasswordField.module.css';
/**
* Component definition for PasswordField
* @public
*/
export const PasswordFieldDefinition = {
classNames: {
root: 'bui-PasswordField',
inputWrapper: 'bui-PasswordFieldInputWrapper',
input: 'bui-PasswordFieldInput',
inputIcon: 'bui-PasswordFieldIcon',
inputVisibility: 'bui-PasswordFieldVisibility',
export const PasswordFieldDefinition = defineComponent<PasswordFieldOwnProps>()(
{
styles,
classNames: {
root: 'bui-PasswordField',
inputWrapper: 'bui-PasswordFieldInputWrapper',
input: 'bui-PasswordFieldInput',
inputIcon: 'bui-PasswordFieldIcon',
inputVisibility: 'bui-PasswordFieldVisibility',
},
propDefs: {
size: { dataAttribute: true, default: 'small' },
className: {},
icon: {},
placeholder: {},
description: {},
secondaryLabel: {},
isRequired: {},
},
},
dataAttributes: {
size: ['small', 'medium'] as const,
},
} as const satisfies ComponentDefinition;
);
@@ -15,27 +15,40 @@
*/
import type { TextFieldProps as AriaTextFieldProps } from 'react-aria-components';
import { ReactNode } from 'react';
import type { ReactNode } from 'react';
import type { Breakpoint } from '../../types';
import type { FieldLabelProps } from '../FieldLabel/types';
/** @public */
export interface PasswordFieldProps
extends AriaTextFieldProps,
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
/**
* An icon to render before the input
*/
icon?: ReactNode;
export type PasswordFieldOwnProps = {
/**
* The size of the password field
* @defaultValue 'medium'
*/
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
className?: string;
/**
* An icon to render before the input
*/
icon?: ReactNode;
/**
* Text to display in the input when it has no value
*/
placeholder?: string;
}
description?: FieldLabelProps['description'];
secondaryLabel?: FieldLabelProps['secondaryLabel'];
isRequired?: boolean;
};
/** @public */
export interface PasswordFieldProps
extends Omit<AriaTextFieldProps, 'className' | 'isRequired' | 'description'>,
Omit<
FieldLabelProps,
'htmlFor' | 'id' | 'className' | 'description' | 'secondaryLabel'
>,
PasswordFieldOwnProps {}