Files
maher 703f50a09d
Some checks failed
Build / run (push) Has been cancelled
first commit
2025-10-29 11:42:25 +01:00

138 lines
3.7 KiB
TypeScript
Executable File

import React, {ComponentPropsWithoutRef, ReactElement, ReactNode} from 'react';
import {Adornment} from './adornment';
import {InputFieldStyle} from './get-input-field-class-names';
import {BaseFieldProps} from './base-field-props';
import {removeEmptyValuesFromObject} from '@common/utils/objects/remove-empty-values-from-object';
import clsx from 'clsx';
export interface FieldProps extends BaseFieldProps {
children: ReactNode;
wrapperProps?: ComponentPropsWithoutRef<'div'>;
labelProps?: ComponentPropsWithoutRef<'label' | 'span'>;
descriptionProps?: ComponentPropsWithoutRef<'div'>;
errorMessageProps?: ComponentPropsWithoutRef<'div'>;
fieldClassNames: InputFieldStyle;
}
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
(props, ref) => {
const {
children,
// Not every component that uses <Field> supports help text.
description,
errorMessage,
descriptionProps = {},
errorMessageProps = {},
startAdornment,
endAdornment,
adornmentPosition,
startAppend,
endAppend,
fieldClassNames,
disabled,
wrapperProps,
} = props;
return (
<div className={fieldClassNames.wrapper} ref={ref} {...wrapperProps}>
<Label {...props} />
<div className={fieldClassNames.inputWrapper}>
<Adornment
direction="start"
className={fieldClassNames.adornment}
position={adornmentPosition}
>
{startAdornment}
</Adornment>
{startAppend && (
<Append style={fieldClassNames.append} disabled={disabled}>
{startAppend}
</Append>
)}
{children}
{endAppend && (
<Append style={fieldClassNames.append} disabled={disabled}>
{endAppend}
</Append>
)}
<Adornment
direction="end"
className={fieldClassNames.adornment}
position={adornmentPosition}
>
{endAdornment}
</Adornment>
</div>
{description && !errorMessage && (
<div className={fieldClassNames.description} {...descriptionProps}>
{description}
</div>
)}
{errorMessage && (
<div className={fieldClassNames.error} {...errorMessageProps}>
{errorMessage}
</div>
)}
</div>
);
},
);
function Label({
labelElementType,
fieldClassNames,
labelProps,
label,
labelSuffix,
labelSuffixPosition = 'spaced',
required,
}: Omit<FieldProps, 'children'>) {
if (!label) {
return null;
}
const ElementType = labelElementType || 'label';
const labelNode = (
<ElementType className={fieldClassNames.label} {...labelProps}>
{label}
{required && <span className="text-danger"> *</span>}
</ElementType>
);
if (labelSuffix) {
return (
<div
className={clsx(
'mb-4 flex w-full gap-4',
labelSuffixPosition === 'spaced' ? 'items-end' : 'items-center',
)}
>
{labelNode}
<div
className={clsx(
'text-xs text-muted',
labelSuffixPosition === 'spaced' ? 'ml-auto' : '',
)}
>
{labelSuffix}
</div>
</div>
);
}
return labelNode;
}
interface AppendProps {
children: ReactElement;
style: InputFieldStyle['append'];
disabled?: boolean;
}
function Append({children, style, disabled}: AppendProps) {
return React.cloneElement(children, {
...children.props,
disabled: children.props.disabled || disabled,
// make sure append styles are not overwritten with empty values
...removeEmptyValuesFromObject(style),
});
}