Skip to content

Commit fc8adcb

Browse files
authored
refactor: field components (#997)
* refactor: field components
1 parent 6d7b6cc commit fc8adcb

11 files changed

+614
-354
lines changed
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as TestRenderer from 'react-test-renderer';
2+
3+
import { Checkbox, type ICheckbox } from './Checkbox';
4+
5+
describe('components/fields/Checkbox.tsx', () => {
6+
const props: ICheckbox = {
7+
name: 'appearance',
8+
label: 'Appearance',
9+
helpText: 'This is some helper text',
10+
checked: true,
11+
onChange: jest.fn(),
12+
};
13+
14+
it('should render', () => {
15+
const tree = TestRenderer.create(<Checkbox {...props} />);
16+
expect(tree).toMatchSnapshot();
17+
});
18+
});

src/components/fields/Checkbox.tsx

+35-30
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,47 @@
1-
interface IFieldCheckbox {
1+
import type { FC, ReactNode } from 'react';
2+
3+
export interface ICheckbox {
24
name: string;
35
label: string;
6+
helpText?: ReactNode | string;
47
checked: boolean;
5-
onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
6-
placeholder?: string;
78
disabled?: boolean;
9+
onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
810
}
911

10-
export const FieldCheckbox = (props: IFieldCheckbox) => {
12+
export const Checkbox: FC<ICheckbox> = (props: ICheckbox) => {
1113
return (
12-
<div className="flex items-start mt-1 mb-3">
13-
<div className="flex items-center h-5">
14-
<input
15-
type="checkbox"
16-
id={props.name}
17-
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
18-
checked={props.checked}
19-
onChange={props.onChange}
20-
disabled={props.disabled}
21-
/>
22-
</div>
14+
<div className="mt-1 mb-3 text-sm">
15+
<div className="flex items-start">
16+
<div className="flex items-center h-5">
17+
<input
18+
type="checkbox"
19+
id={props.name}
20+
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
21+
checked={props.checked}
22+
onChange={props.onChange}
23+
disabled={props.disabled}
24+
/>
25+
</div>
2326

24-
<div className="ml-3 text-sm">
25-
<label
26-
htmlFor={props.name}
27-
className="font-medium text-gray-700 dark:text-gray-200"
28-
style={
29-
props.disabled ? { textDecoration: 'line-through' } : undefined
30-
}
31-
>
32-
{props.label}
33-
</label>
34-
{props.placeholder && (
35-
<div className="italic text-gray-500 dark:text-gray-300">
36-
{props.placeholder}
37-
</div>
38-
)}
27+
<div className="ml-3 ">
28+
<label
29+
htmlFor={props.name}
30+
className="font-medium text-gray-700 dark:text-gray-200"
31+
style={
32+
props.disabled ? { textDecoration: 'line-through' } : undefined
33+
}
34+
>
35+
{props.label}
36+
</label>
37+
</div>
3938
</div>
39+
40+
{props.helpText && (
41+
<div className="text-xs mt-1 italic text-gray-500 dark:text-gray-300">
42+
{props.helpText}
43+
</div>
44+
)}
4045
</div>
4146
);
4247
};
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as TestRenderer from 'react-test-renderer';
2+
3+
import { Form } from 'react-final-form';
4+
import { FieldInput, type IFieldInput } from './FieldInput';
5+
6+
describe('components/fields/FieldInput.tsx', () => {
7+
const props: IFieldInput = {
8+
name: 'appearance',
9+
label: 'Appearance',
10+
placeholder: 'This is some placeholder text',
11+
helpText: 'This is some helper text',
12+
};
13+
14+
it('should render', () => {
15+
const tree = TestRenderer.create(
16+
<Form
17+
onSubmit={() => {}}
18+
hand
19+
render={({ handleSubmit }) => (
20+
<form onSubmit={handleSubmit}>
21+
<FieldInput {...props} />
22+
</form>
23+
)}
24+
/>,
25+
);
26+
expect(tree).toMatchSnapshot();
27+
});
28+
});

src/components/fields/FieldInput.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FC, ReactNode } from 'react';
22
import { Field } from 'react-final-form';
33

4-
export interface IProps {
4+
export interface IFieldInput {
55
name: string;
66
type?: string;
77
label: string;
@@ -10,10 +10,10 @@ export interface IProps {
1010
required?: boolean;
1111
}
1212

13-
export const FieldInput: FC<IProps> = ({
13+
export const FieldInput: FC<IFieldInput> = ({
1414
label,
1515
name,
16-
placeholder = '',
16+
placeholder,
1717
helpText,
1818
type = 'text',
1919
required = false,

src/components/fields/RadioGroup.test.tsx

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { fireEvent, render, screen } from '@testing-library/react';
22

33
import * as TestRenderer from 'react-test-renderer';
44

5-
import { FieldRadioGroup } from './RadioGroup';
5+
import { type IRadioGroup, RadioGroup } from './RadioGroup';
66

7-
describe('components/fields/radiogroup.tsx', () => {
8-
const props = {
7+
describe('components/fields/RadioGroup.tsx', () => {
8+
const props: IRadioGroup = {
99
label: 'Appearance',
1010
name: 'appearance',
11-
placeholder: 'This is some helper text',
11+
helpText: 'This is some helper text',
1212
options: [
1313
{ label: 'Value 1', value: 'one' },
1414
{ label: 'Value 2', value: 'two' },
@@ -17,13 +17,20 @@ describe('components/fields/radiogroup.tsx', () => {
1717
value: 'two',
1818
};
1919

20-
it('should render ', () => {
21-
const tree = TestRenderer.create(<FieldRadioGroup {...props} />);
20+
it('should render', () => {
21+
const tree = TestRenderer.create(<RadioGroup {...props} />);
22+
expect(tree).toMatchSnapshot();
23+
});
24+
25+
it('should render as disabled', () => {
26+
const mockProps = { ...props, disabled: true };
27+
28+
const tree = TestRenderer.create(<RadioGroup {...mockProps} />);
2229
expect(tree).toMatchSnapshot();
2330
});
2431

2532
it('should check that NProgress is getting called in getDerivedStateFromProps (loading)', () => {
26-
render(<FieldRadioGroup {...props} />);
33+
render(<RadioGroup {...props} />);
2734
fireEvent.click(screen.getByLabelText('Value 1'));
2835
expect(props.onChange).toHaveBeenCalledTimes(1);
2936
});

src/components/fields/RadioGroup.tsx

+57-55
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,70 @@
1-
import type { ChangeEvent } from 'react';
1+
import type { ChangeEvent, FC, ReactNode } from 'react';
22
import type { RadioGroupItem } from '../../types';
33

4-
export const FieldRadioGroup = ({
5-
label,
6-
placeholder,
7-
name,
8-
options,
9-
onChange,
10-
value,
11-
}: {
4+
export interface IRadioGroup {
125
name: string;
136
label: string;
14-
placeholder?: string;
7+
helpText?: ReactNode | string;
158
options: RadioGroupItem[];
16-
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
179
value: string;
18-
}) => {
19-
return (
20-
<div className="flex items-start mt-1 mb-3">
21-
<div className="mr-3 text-sm py-1">
22-
<label
23-
htmlFor={name}
24-
className="font-medium text-gray-700 dark:text-gray-200 "
25-
>
26-
{label}
27-
</label>
10+
disabled?: boolean;
11+
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
12+
}
2813

29-
{placeholder && (
30-
<div className="italic text-gray-500 dark:text-gray-300">
31-
{placeholder}
32-
</div>
33-
)}
34-
</div>
14+
export const RadioGroup: FC<IRadioGroup> = (props: IRadioGroup) => {
15+
return (
16+
<div className="mt-1 mb-3 text-sm">
17+
<div className="flex items-start">
18+
<div className="mr-3 py-1">
19+
<label
20+
htmlFor={props.name}
21+
className="font-medium text-gray-700 dark:text-gray-200 "
22+
style={
23+
props.disabled ? { textDecoration: 'line-through' } : undefined
24+
}
25+
>
26+
{props.label}
27+
</label>
28+
</div>
3529

36-
<div
37-
className="flex items-center space-x-4"
38-
role="group"
39-
aria-labelledby={name}
40-
>
41-
{options.map((item) => {
42-
return (
43-
<div
44-
className="flex mt-1"
45-
key={`radio_item_${item.value.toLowerCase()}`}
46-
>
47-
<input
48-
type="radio"
49-
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
50-
id={`${name}_${item.value.toLowerCase()}`}
51-
name={name}
52-
value={item.value}
53-
onChange={onChange}
54-
checked={item.value === value}
55-
/>
56-
<label
57-
htmlFor={`${name}_${item.value.toLowerCase()}`}
58-
className="ml-3 block text-sm font-medium text-gray-700 dark:text-white"
30+
<div
31+
className="flex items-center space-x-4"
32+
role="group"
33+
aria-labelledby={props.name}
34+
>
35+
{props.options.map((item) => {
36+
return (
37+
<div
38+
className="flex mt-1"
39+
key={`radio_item_${item.value.toLowerCase()}`}
5940
>
60-
{item.label}
61-
</label>
62-
</div>
63-
);
64-
})}
41+
<input
42+
type="radio"
43+
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
44+
id={`${props.name}_${item.value.toLowerCase()}`}
45+
name={props.name}
46+
value={item.value}
47+
onChange={props.onChange}
48+
checked={item.value === props.value}
49+
disabled={props.disabled}
50+
/>
51+
<label
52+
htmlFor={`${props.name}_${item.value.toLowerCase()}`}
53+
className="ml-3 block text-sm font-medium text-gray-700 dark:text-white"
54+
>
55+
{item.label}
56+
</label>
57+
</div>
58+
);
59+
})}
60+
</div>
6561
</div>
62+
63+
{props.helpText && (
64+
<div className="text-xs mt-1 italic text-gray-500 dark:text-gray-300">
65+
{props.helpText}
66+
</div>
67+
)}
6668
</div>
6769
);
6870
};

src/components/fields/__snapshots__/Checkbox.test.tsx.snap

+38
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/fields/__snapshots__/FieldInput.test.tsx.snap

+35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)