brunner56's picture
implement app
0bfe2e3
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling';
/* -------------------------------------------------------------------------------------------------
* Anatomy
* -----------------------------------------------------------------------------------------------*/
export const BasicFieldAnatomy = defineStyleAnatomy({
fieldLabel: cva([
'UI-BasicField__fieldLabel',
'text-base w-fit font-semibold self-start',
'data-[error=true]:text-red-500',
]),
fieldAsterisk: cva('UI-BasicField__fieldAsterisk ml-1 text-red-500 text-sm'),
fieldDetails: cva('UI-BasicField__fieldDetails'),
field: cva('UI-BasicField__field relative w-full space-y-1'),
fieldHelpText: cva('UI-BasicField__fieldHelpText text-sm text-[--muted]'),
fieldErrorText: cva('UI-BasicField__fieldErrorText text-sm text-red-500'),
});
/* -------------------------------------------------------------------------------------------------
* BasicFieldOptions
* - Field components inherit these props
* -----------------------------------------------------------------------------------------------*/
export type BasicFieldOptions = ComponentAnatomy<typeof BasicFieldAnatomy> & {
/**
* The id of the field. If not provided, a unique id will be generated.
*/
id?: string | undefined;
/**
* The form field name.
*/
name?: string;
/**
* The label of the field.
*/
label?: React.ReactNode;
/**
* Additional props to pass to the label element.
*/
labelProps?: React.LabelHTMLAttributes<HTMLLabelElement>;
/**
* Help or description text to display below the field.
*/
help?: React.ReactNode;
/**
* Error text to display below the field.
*/
error?: string;
/**
* If `true`, the field will be required.
*/
required?: boolean;
/**
* If `true`, the field will be disabled.
*/
disabled?: boolean;
/**
* If `true`, the field will be readonly.
*/
readonly?: boolean;
};
/* -------------------------------------------------------------------------------------------------
* Extract BasicFieldProps
* -----------------------------------------------------------------------------------------------*/
export function extractBasicFieldProps<Props extends BasicFieldOptions>(
props: Props,
id: string
) {
const {
name,
label,
labelProps,
help,
error,
required,
disabled = false,
readonly = false,
fieldDetailsClass,
fieldLabelClass,
fieldAsteriskClass,
fieldClass,
fieldErrorTextClass,
fieldHelpTextClass,
id: _id,
...rest
} = props;
return [
rest,
{
id: _id || id,
name,
label,
help,
error,
disabled,
required,
readonly,
fieldAsteriskClass,
fieldErrorTextClass,
fieldHelpTextClass,
fieldDetailsClass,
fieldLabelClass,
fieldClass,
labelProps,
},
] as [
Omit<
Props,
| 'label'
| 'name'
| 'help'
| 'error'
| 'disabled'
| 'required'
| 'readonly'
| 'fieldDetailsClass'
| 'fieldLabelClass'
| 'fieldClass'
| 'fieldHelpTextClass'
| 'fieldErrorTextClass'
| 'id'
| 'labelProps'
| 'fieldAsteriskClass'
>,
Omit<BasicFieldOptions, 'id'> & {
id: string;
},
];
}
/* -------------------------------------------------------------------------------------------------
* BasicField
* -----------------------------------------------------------------------------------------------*/
export type BasicFieldProps = React.ComponentPropsWithoutRef<'div'> &
BasicFieldOptions;
export const BasicField = React.memo(
React.forwardRef<HTMLDivElement, BasicFieldProps>((props, ref) => {
const {
children,
className,
labelProps,
id,
label,
error,
help,
disabled,
readonly,
required,
fieldClass,
fieldDetailsClass,
fieldLabelClass,
fieldAsteriskClass,
fieldErrorTextClass,
fieldHelpTextClass,
...rest
} = props;
return (
<div
className={cn(BasicFieldAnatomy.field(), className, fieldClass)}
{...rest}
ref={ref}
>
{!!label && (
<label
htmlFor={disabled ? undefined : id}
className={cn(BasicFieldAnatomy.fieldLabel(), fieldLabelClass)}
data-error={!!error}
{...labelProps}
>
{label}
{required && (
<span
className={cn(
BasicFieldAnatomy.fieldAsterisk(),
fieldAsteriskClass
)}
>
*
</span>
)}
</label>
)}
{children}
{(!!help || !!error) && (
<div
className={cn(BasicFieldAnatomy.fieldDetails(), fieldDetailsClass)}
>
{!!help && (
<div
className={cn(
BasicFieldAnatomy.fieldHelpText(),
fieldHelpTextClass
)}
>
{help}
</div>
)}
{!!error && (
<div
className={cn(
BasicFieldAnatomy.fieldErrorText(),
fieldErrorTextClass
)}
>
{error}
</div>
)}
</div>
)}
</div>
);
})
);
BasicField.displayName = 'BasicField';