Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: field components #997

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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