Skip to content

Commit

Permalink
refactor: field components (#997)
Browse files Browse the repository at this point in the history
* refactor: field components
  • Loading branch information
setchy authored Apr 11, 2024
1 parent 6d7b6cc commit fc8adcb
Show file tree
Hide file tree
Showing 11 changed files with 614 additions and 354 deletions.
18 changes: 18 additions & 0 deletions src/components/fields/Checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as TestRenderer from 'react-test-renderer';

import { Checkbox, type ICheckbox } from './Checkbox';

describe('components/fields/Checkbox.tsx', () => {
const props: ICheckbox = {
name: 'appearance',
label: 'Appearance',
helpText: 'This is some helper text',
checked: true,
onChange: jest.fn(),
};

it('should render', () => {
const tree = TestRenderer.create(<Checkbox {...props} />);
expect(tree).toMatchSnapshot();
});
});
65 changes: 35 additions & 30 deletions src/components/fields/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,47 @@
interface IFieldCheckbox {
import type { FC, ReactNode } from 'react';

export interface ICheckbox {
name: string;
label: string;
helpText?: ReactNode | string;
checked: boolean;
onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
disabled?: boolean;
onChange: (evt: React.ChangeEvent<HTMLInputElement>) => void;
}

export const FieldCheckbox = (props: IFieldCheckbox) => {
export const Checkbox: FC<ICheckbox> = (props: ICheckbox) => {
return (
<div className="flex items-start mt-1 mb-3">
<div className="flex items-center h-5">
<input
type="checkbox"
id={props.name}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
checked={props.checked}
onChange={props.onChange}
disabled={props.disabled}
/>
</div>
<div className="mt-1 mb-3 text-sm">
<div className="flex items-start">
<div className="flex items-center h-5">
<input
type="checkbox"
id={props.name}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
checked={props.checked}
onChange={props.onChange}
disabled={props.disabled}
/>
</div>

<div className="ml-3 text-sm">
<label
htmlFor={props.name}
className="font-medium text-gray-700 dark:text-gray-200"
style={
props.disabled ? { textDecoration: 'line-through' } : undefined
}
>
{props.label}
</label>
{props.placeholder && (
<div className="italic text-gray-500 dark:text-gray-300">
{props.placeholder}
</div>
)}
<div className="ml-3 ">
<label
htmlFor={props.name}
className="font-medium text-gray-700 dark:text-gray-200"
style={
props.disabled ? { textDecoration: 'line-through' } : undefined
}
>
{props.label}
</label>
</div>
</div>

{props.helpText && (
<div className="text-xs mt-1 italic text-gray-500 dark:text-gray-300">
{props.helpText}
</div>
)}
</div>
);
};
28 changes: 28 additions & 0 deletions src/components/fields/FieldInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as TestRenderer from 'react-test-renderer';

import { Form } from 'react-final-form';
import { FieldInput, type IFieldInput } from './FieldInput';

describe('components/fields/FieldInput.tsx', () => {
const props: IFieldInput = {
name: 'appearance',
label: 'Appearance',
placeholder: 'This is some placeholder text',
helpText: 'This is some helper text',
};

it('should render', () => {
const tree = TestRenderer.create(
<Form
onSubmit={() => {}}
hand
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<FieldInput {...props} />
</form>
)}
/>,
);
expect(tree).toMatchSnapshot();
});
});
6 changes: 3 additions & 3 deletions src/components/fields/FieldInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FC, ReactNode } from 'react';
import { Field } from 'react-final-form';

export interface IProps {
export interface IFieldInput {
name: string;
type?: string;
label: string;
Expand All @@ -10,10 +10,10 @@ export interface IProps {
required?: boolean;
}

export const FieldInput: FC<IProps> = ({
export const FieldInput: FC<IFieldInput> = ({
label,
name,
placeholder = '',
placeholder,
helpText,
type = 'text',
required = false,
Expand Down
21 changes: 14 additions & 7 deletions src/components/fields/RadioGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { fireEvent, render, screen } from '@testing-library/react';

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

import { FieldRadioGroup } from './RadioGroup';
import { type IRadioGroup, RadioGroup } from './RadioGroup';

describe('components/fields/radiogroup.tsx', () => {
const props = {
describe('components/fields/RadioGroup.tsx', () => {
const props: IRadioGroup = {
label: 'Appearance',
name: 'appearance',
placeholder: 'This is some helper text',
helpText: 'This is some helper text',
options: [
{ label: 'Value 1', value: 'one' },
{ label: 'Value 2', value: 'two' },
Expand All @@ -17,13 +17,20 @@ describe('components/fields/radiogroup.tsx', () => {
value: 'two',
};

it('should render ', () => {
const tree = TestRenderer.create(<FieldRadioGroup {...props} />);
it('should render', () => {
const tree = TestRenderer.create(<RadioGroup {...props} />);
expect(tree).toMatchSnapshot();
});

it('should render as disabled', () => {
const mockProps = { ...props, disabled: true };

const tree = TestRenderer.create(<RadioGroup {...mockProps} />);
expect(tree).toMatchSnapshot();
});

it('should check that NProgress is getting called in getDerivedStateFromProps (loading)', () => {
render(<FieldRadioGroup {...props} />);
render(<RadioGroup {...props} />);
fireEvent.click(screen.getByLabelText('Value 1'));
expect(props.onChange).toHaveBeenCalledTimes(1);
});
Expand Down
112 changes: 57 additions & 55 deletions src/components/fields/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,70 @@
import type { ChangeEvent } from 'react';
import type { ChangeEvent, FC, ReactNode } from 'react';
import type { RadioGroupItem } from '../../types';

export const FieldRadioGroup = ({
label,
placeholder,
name,
options,
onChange,
value,
}: {
export interface IRadioGroup {
name: string;
label: string;
placeholder?: string;
helpText?: ReactNode | string;
options: RadioGroupItem[];
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
value: string;
}) => {
return (
<div className="flex items-start mt-1 mb-3">
<div className="mr-3 text-sm py-1">
<label
htmlFor={name}
className="font-medium text-gray-700 dark:text-gray-200 "
>
{label}
</label>
disabled?: boolean;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
}

{placeholder && (
<div className="italic text-gray-500 dark:text-gray-300">
{placeholder}
</div>
)}
</div>
export const RadioGroup: FC<IRadioGroup> = (props: IRadioGroup) => {
return (
<div className="mt-1 mb-3 text-sm">
<div className="flex items-start">
<div className="mr-3 py-1">
<label
htmlFor={props.name}
className="font-medium text-gray-700 dark:text-gray-200 "
style={
props.disabled ? { textDecoration: 'line-through' } : undefined
}
>
{props.label}
</label>
</div>

<div
className="flex items-center space-x-4"
role="group"
aria-labelledby={name}
>
{options.map((item) => {
return (
<div
className="flex mt-1"
key={`radio_item_${item.value.toLowerCase()}`}
>
<input
type="radio"
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
id={`${name}_${item.value.toLowerCase()}`}
name={name}
value={item.value}
onChange={onChange}
checked={item.value === value}
/>
<label
htmlFor={`${name}_${item.value.toLowerCase()}`}
className="ml-3 block text-sm font-medium text-gray-700 dark:text-white"
<div
className="flex items-center space-x-4"
role="group"
aria-labelledby={props.name}
>
{props.options.map((item) => {
return (
<div
className="flex mt-1"
key={`radio_item_${item.value.toLowerCase()}`}
>
{item.label}
</label>
</div>
);
})}
<input
type="radio"
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
id={`${props.name}_${item.value.toLowerCase()}`}
name={props.name}
value={item.value}
onChange={props.onChange}
checked={item.value === props.value}
disabled={props.disabled}
/>
<label
htmlFor={`${props.name}_${item.value.toLowerCase()}`}
className="ml-3 block text-sm font-medium text-gray-700 dark:text-white"
>
{item.label}
</label>
</div>
);
})}
</div>
</div>

{props.helpText && (
<div className="text-xs mt-1 italic text-gray-500 dark:text-gray-300">
{props.helpText}
</div>
)}
</div>
);
};
38 changes: 38 additions & 0 deletions src/components/fields/__snapshots__/Checkbox.test.tsx.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions src/components/fields/__snapshots__/FieldInput.test.tsx.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fc8adcb

Please sign in to comment.