138
common/resources/client/ui/forms/input-field/use-field.ts
Executable file
138
common/resources/client/ui/forms/input-field/use-field.ts
Executable file
@@ -0,0 +1,138 @@
|
||||
import {HTMLAttributes, HTMLProps, RefObject, useId} from 'react';
|
||||
import {BaseFieldPropsWithDom} from './base-field-props';
|
||||
import {useAutoFocus} from '../../focus/use-auto-focus';
|
||||
import type {FieldProps} from './field';
|
||||
|
||||
interface UseFieldReturn<T> {
|
||||
fieldProps: Omit<FieldProps, 'fieldClassNames' | 'children'>;
|
||||
inputProps: HTMLAttributes<T>;
|
||||
}
|
||||
|
||||
interface Props<T> extends BaseFieldPropsWithDom<T> {
|
||||
focusRef: RefObject<HTMLElement>;
|
||||
}
|
||||
export function useField<T>(props: Props<T>): UseFieldReturn<T> {
|
||||
const {
|
||||
focusRef,
|
||||
labelElementType = 'label',
|
||||
label,
|
||||
labelSuffix,
|
||||
labelSuffixPosition,
|
||||
autoFocus,
|
||||
autoSelectText,
|
||||
labelPosition,
|
||||
descriptionPosition,
|
||||
size,
|
||||
errorMessage,
|
||||
description,
|
||||
flexibleHeight,
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
startAppend,
|
||||
adornmentPosition,
|
||||
endAppend,
|
||||
className,
|
||||
inputClassName,
|
||||
inputWrapperClassName,
|
||||
unstyled,
|
||||
background,
|
||||
invalid,
|
||||
disabled,
|
||||
id,
|
||||
inputRadius,
|
||||
inputBorder,
|
||||
inputShadow,
|
||||
inputRing,
|
||||
inputFontSize,
|
||||
...inputDomProps
|
||||
} = props;
|
||||
|
||||
useAutoFocus(props, focusRef);
|
||||
|
||||
const defaultId = useId();
|
||||
const inputId = id || defaultId;
|
||||
const labelId = `${inputId}-label`;
|
||||
const descriptionId = `${inputId}-description`;
|
||||
const errorId = `${inputId}-error`;
|
||||
|
||||
const labelProps = {
|
||||
id: labelId,
|
||||
htmlFor: labelElementType === 'label' ? inputId : undefined,
|
||||
};
|
||||
const descriptionProps = {
|
||||
id: descriptionId,
|
||||
};
|
||||
const errorMessageProps = {
|
||||
id: errorId,
|
||||
};
|
||||
|
||||
const ariaLabel =
|
||||
!props.label && !props['aria-label'] && props.placeholder
|
||||
? props.placeholder
|
||||
: props['aria-label'];
|
||||
|
||||
const inputProps: HTMLProps<T> = {
|
||||
'aria-label': ariaLabel,
|
||||
'aria-invalid': invalid || undefined,
|
||||
id: inputId,
|
||||
disabled,
|
||||
...inputDomProps,
|
||||
};
|
||||
|
||||
const labelledBy = [];
|
||||
if (label) {
|
||||
labelledBy.push(labelProps.id);
|
||||
}
|
||||
if (inputProps['aria-labelledby']) {
|
||||
labelledBy.push(inputProps['aria-labelledby']);
|
||||
}
|
||||
inputProps['aria-labelledby'] = labelledBy.length
|
||||
? labelledBy.join(' ')
|
||||
: undefined;
|
||||
|
||||
const describedBy = [];
|
||||
if (description) {
|
||||
describedBy.push(descriptionProps.id);
|
||||
}
|
||||
if (errorMessage) {
|
||||
describedBy.push(errorMessageProps.id);
|
||||
}
|
||||
if (inputProps['aria-describedby']) {
|
||||
describedBy.push(inputProps['aria-describedby']);
|
||||
}
|
||||
inputProps['aria-describedby'] = describedBy.length
|
||||
? describedBy.join(' ')
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
fieldProps: {
|
||||
errorMessageProps,
|
||||
descriptionProps,
|
||||
labelProps,
|
||||
disabled,
|
||||
label,
|
||||
labelSuffix,
|
||||
labelSuffixPosition,
|
||||
autoFocus,
|
||||
autoSelectText,
|
||||
labelPosition,
|
||||
descriptionPosition,
|
||||
size,
|
||||
errorMessage,
|
||||
description,
|
||||
flexibleHeight,
|
||||
startAdornment,
|
||||
endAdornment,
|
||||
startAppend,
|
||||
adornmentPosition,
|
||||
endAppend,
|
||||
className,
|
||||
inputClassName,
|
||||
inputWrapperClassName,
|
||||
unstyled,
|
||||
background,
|
||||
invalid,
|
||||
},
|
||||
inputProps,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user