soapbox/app/soapbox/components/ui/form-group/form-group.tsx

112 wiersze
3.0 KiB
TypeScript
Czysty Zwykły widok Historia

2022-03-21 18:09:01 +00:00
import React, { useMemo } from 'react';
import { v4 as uuidv4 } from 'uuid';
2022-05-09 13:52:41 +00:00
import Checkbox from '../checkbox/checkbox';
import HStack from '../hstack/hstack';
import Stack from '../stack/stack';
2022-03-21 18:09:01 +00:00
interface IFormGroup {
/** Input label message. */
labelText?: React.ReactNode
2022-06-07 18:55:34 +00:00
/** Input label tooltip message. */
labelTitle?: string
/** Input hint message. */
hintText?: React.ReactNode
/** Input errors. */
2022-03-21 18:09:01 +00:00
errors?: string[]
2023-01-10 23:03:15 +00:00
/** Elements to display within the FormGroup. */
children: React.ReactNode
2022-03-21 18:09:01 +00:00
}
2022-05-06 23:51:06 +00:00
/** Input container with label. Renders the child. */
2022-03-21 18:09:01 +00:00
const FormGroup: React.FC<IFormGroup> = (props) => {
2022-06-07 18:55:34 +00:00
const { children, errors = [], labelText, labelTitle, hintText } = props;
2022-03-21 18:09:01 +00:00
const formFieldId: string = useMemo(() => `field-${uuidv4()}`, []);
const inputChildren = React.Children.toArray(children);
const hasError = errors?.length > 0;
2022-03-21 18:09:01 +00:00
let firstChild;
if (React.isValidElement(inputChildren[0])) {
firstChild = React.cloneElement(
inputChildren[0],
{ id: formFieldId },
2022-03-21 18:09:01 +00:00
);
}
2022-05-09 13:52:41 +00:00
const isCheckboxFormGroup = firstChild?.type === Checkbox;
if (isCheckboxFormGroup) {
return (
<HStack alignItems='start' space={2}>
{firstChild}
<Stack>
{labelText && (
<label
htmlFor={formFieldId}
data-testid='form-group-label'
className='-mt-0.5 block text-sm font-medium text-gray-900 dark:text-gray-100'
2022-06-07 18:55:34 +00:00
title={labelTitle}
2022-05-09 13:52:41 +00:00
>
{labelText}
</label>
)}
{hasError && (
2022-05-09 13:52:41 +00:00
<div>
<p
data-testid='form-group-error'
2023-02-01 22:13:42 +00:00
className='form-error relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
2022-05-09 13:52:41 +00:00
>
{errors.join(', ')}
</p>
</div>
)}
{hintText && (
<p data-testid='form-group-hint' className='mt-0.5 text-xs text-gray-700 dark:text-gray-600'>
2022-05-09 13:52:41 +00:00
{hintText}
</p>
)}
</Stack>
</HStack>
);
}
2022-03-21 18:09:01 +00:00
return (
<div>
2022-05-05 22:45:32 +00:00
{labelText && (
<label
htmlFor={formFieldId}
data-testid='form-group-label'
className='block text-sm font-medium text-gray-900 dark:text-gray-100'
2022-06-07 18:55:34 +00:00
title={labelTitle}
2022-05-05 22:45:32 +00:00
>
{labelText}
</label>
)}
2022-03-21 18:09:01 +00:00
<div className='mt-1 dark:text-white'>
2022-03-21 18:09:01 +00:00
{firstChild}
{inputChildren.filter((_, i) => i !== 0)}
{hasError && (
<p
data-testid='form-group-error'
2023-02-01 22:13:42 +00:00
className='form-error relative mt-0.5 inline-block rounded-md bg-danger-200 px-2 py-1 text-xs text-danger-900'
>
2022-03-21 18:09:01 +00:00
{errors.join(', ')}
</p>
)}
2022-05-05 22:45:32 +00:00
{hintText && (
<p data-testid='form-group-hint' className='mt-0.5 text-xs text-gray-700 dark:text-gray-600'>
2022-03-21 18:09:01 +00:00
{hintText}
</p>
2022-05-05 22:45:32 +00:00
)}
2022-03-21 18:09:01 +00:00
</div>
</div>
);
};
export default FormGroup;