Migrate TextField component from useStyles to useDefinition
Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -16,48 +16,36 @@
|
||||
|
||||
import { forwardRef, useEffect } from 'react';
|
||||
import { Input, TextField as AriaTextField } from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
import type { TextFieldProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import { TextFieldDefinition } from './definition';
|
||||
import styles from './TextField.module.css';
|
||||
|
||||
/** @public */
|
||||
export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
label,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (!label && !ariaLabel && !ariaLabelledBy) {
|
||||
console.warn(
|
||||
'TextField requires either a visible label, aria-label, or aria-labelledby for accessibility',
|
||||
);
|
||||
}
|
||||
}, [label, ariaLabel, ariaLabelledBy]);
|
||||
|
||||
const { classNames, dataAttributes, style, cleanedProps } = useStyles(
|
||||
const { ownProps, restProps, dataAttributes } = useDefinition(
|
||||
TextFieldDefinition,
|
||||
{
|
||||
size: 'small',
|
||||
...props,
|
||||
},
|
||||
props,
|
||||
);
|
||||
|
||||
const {
|
||||
className,
|
||||
description,
|
||||
classes,
|
||||
label,
|
||||
icon,
|
||||
isRequired,
|
||||
secondaryLabel,
|
||||
placeholder,
|
||||
...rest
|
||||
} = cleanedProps;
|
||||
description,
|
||||
} = ownProps;
|
||||
|
||||
useEffect(() => {
|
||||
if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) {
|
||||
console.warn(
|
||||
'TextField requires either a visible label, aria-label, or aria-labelledby for accessibility',
|
||||
);
|
||||
}
|
||||
}, [label, restProps['aria-label'], restProps['aria-labelledby']]);
|
||||
|
||||
// If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required.
|
||||
const secondaryLabelText =
|
||||
@@ -65,12 +53,9 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
|
||||
return (
|
||||
<AriaTextField
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
className={classes.root}
|
||||
{...dataAttributes}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
style={style}
|
||||
{...rest}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
<FieldLabel
|
||||
@@ -79,18 +64,12 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
description={description}
|
||||
/>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.inputWrapper,
|
||||
styles[classNames.inputWrapper],
|
||||
)}
|
||||
className={classes.inputWrapper}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon && (
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.inputIcon,
|
||||
styles[classNames.inputIcon],
|
||||
)}
|
||||
className={classes.inputIcon}
|
||||
data-size={dataAttributes['data-size']}
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -98,7 +77,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
className={clsx(classNames.input, styles[classNames.input])}
|
||||
className={classes.input}
|
||||
{...(icon && { 'data-icon': true })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentDefinition } from '../../types';
|
||||
import { defineComponent } from '../../hooks/useDefinition';
|
||||
import type { TextFieldOwnProps } from './types';
|
||||
import styles from './TextField.module.css';
|
||||
|
||||
/**
|
||||
* Component definition for TextField
|
||||
* @public
|
||||
*/
|
||||
export const TextFieldDefinition = {
|
||||
export const TextFieldDefinition = defineComponent<TextFieldOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-TextField',
|
||||
inputWrapper: 'bui-InputWrapper',
|
||||
@@ -28,9 +31,14 @@ export const TextFieldDefinition = {
|
||||
inputIcon: 'bui-InputIcon',
|
||||
inputAction: 'bui-InputAction',
|
||||
},
|
||||
dataAttributes: {
|
||||
invalid: [true, false] as const,
|
||||
disabled: [true, false] as const,
|
||||
size: ['small', 'medium'] as const,
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
className: {},
|
||||
icon: {},
|
||||
placeholder: {},
|
||||
label: {},
|
||||
description: {},
|
||||
secondaryLabel: {},
|
||||
isRequired: {},
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
});
|
||||
|
||||
@@ -15,14 +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 type TextFieldOwnProps = {
|
||||
/**
|
||||
* The size of the text 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;
|
||||
|
||||
label?: FieldLabelProps['label'];
|
||||
description?: FieldLabelProps['description'];
|
||||
secondaryLabel?: FieldLabelProps['secondaryLabel'];
|
||||
isRequired?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface TextFieldProps
|
||||
extends AriaTextFieldProps,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
|
||||
extends Omit<AriaTextFieldProps, 'className' | 'isRequired' | 'description'>,
|
||||
TextFieldOwnProps {
|
||||
/**
|
||||
* The HTML input type for the text field
|
||||
*
|
||||
@@ -31,19 +57,4 @@ export interface TextFieldProps
|
||||
* search inputs and `PasswordField` for password inputs.
|
||||
*/
|
||||
type?: 'text' | 'email' | 'tel' | 'url';
|
||||
/**
|
||||
* An icon to render before the input
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
|
||||
/**
|
||||
* The size of the text field
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
|
||||
/**
|
||||
* Text to display in the input when it has no value
|
||||
*/
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user