aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/atoms/forms/field.stories.tsx12
-rw-r--r--src/components/atoms/forms/field.test.tsx20
-rw-r--r--src/components/atoms/forms/field.tsx8
-rw-r--r--src/components/molecules/forms/labelled-field.stories.tsx180
-rw-r--r--src/components/molecules/forms/labelled-field.test.tsx19
-rw-r--r--src/components/molecules/forms/labelled-field.tsx25
6 files changed, 250 insertions, 14 deletions
diff --git a/src/components/atoms/forms/field.stories.tsx b/src/components/atoms/forms/field.stories.tsx
index 0406f10..02681e7 100644
--- a/src/components/atoms/forms/field.stories.tsx
+++ b/src/components/atoms/forms/field.stories.tsx
@@ -31,12 +31,9 @@ export default {
type: 'text',
},
description: 'Field id.',
- table: {
- category: 'Options',
- },
type: {
name: 'string',
- required: false,
+ required: true,
},
},
max: {
@@ -70,12 +67,9 @@ export default {
type: 'text',
},
description: 'Field name.',
- table: {
- category: 'Options',
- },
type: {
name: 'string',
- required: false,
+ required: true,
},
},
placeholder: {
@@ -171,6 +165,8 @@ const Template: ComponentStory<typeof FieldComponent> = (args) => (
export const Field = Template.bind({});
Field.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
setValue: () => null,
value: '',
};
diff --git a/src/components/atoms/forms/field.test.tsx b/src/components/atoms/forms/field.test.tsx
index 5488220..a04a976 100644
--- a/src/components/atoms/forms/field.test.tsx
+++ b/src/components/atoms/forms/field.test.tsx
@@ -3,12 +3,28 @@ import Field from './field';
describe('Field', () => {
it('renders a text input', () => {
- render(<Field type="text" value="" setValue={() => null} />);
+ render(
+ <Field
+ id="text-field"
+ name="text-field"
+ type="text"
+ value=""
+ setValue={() => null}
+ />
+ );
expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text');
});
it('renders a search input', () => {
- render(<Field type="search" value="" setValue={() => null} />);
+ render(
+ <Field
+ id="search-field"
+ name="search-field"
+ type="search"
+ value=""
+ setValue={() => null}
+ />
+ );
expect(screen.getByRole('searchbox')).toHaveAttribute('type', 'search');
});
});
diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx
index 7d1ac93..513d2ba 100644
--- a/src/components/atoms/forms/field.tsx
+++ b/src/components/atoms/forms/field.tsx
@@ -1,7 +1,7 @@
import { ChangeEvent, FC, SetStateAction } from 'react';
import styles from './forms.module.scss';
-type FieldType =
+export type FieldType =
| 'datetime-local'
| 'email'
| 'number'
@@ -12,7 +12,7 @@ type FieldType =
| 'time'
| 'url';
-type FieldProps = {
+export type FieldProps = {
/**
* Field state. Either enabled (false) or disabled (true).
*/
@@ -20,7 +20,7 @@ type FieldProps = {
/**
* Field id attribute.
*/
- id?: string;
+ id: string;
/**
* Field maximum value.
*/
@@ -32,7 +32,7 @@ type FieldProps = {
/**
* Field name attribute.
*/
- name?: string;
+ name: string;
/**
* Placeholder value.
*/
diff --git a/src/components/molecules/forms/labelled-field.stories.tsx b/src/components/molecules/forms/labelled-field.stories.tsx
new file mode 100644
index 0000000..eb7f8b5
--- /dev/null
+++ b/src/components/molecules/forms/labelled-field.stories.tsx
@@ -0,0 +1,180 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import LabelledFieldComponent from './labelled-field';
+
+export default {
+ title: 'Molecules/Forms',
+ component: LabelledFieldComponent,
+ args: {
+ disabled: false,
+ required: false,
+ },
+ argTypes: {
+ disabled: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Field state: either enabled or disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ label: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field label.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ max: {
+ control: {
+ type: 'number',
+ },
+ description: 'Maximum value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'number',
+ required: false,
+ },
+ },
+ min: {
+ control: {
+ type: 'number',
+ },
+ description: 'Minimum value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'number',
+ required: false,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ placeholder: {
+ control: {
+ type: 'text',
+ },
+ description: 'A placeholder value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ required: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Determine if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ setValue: {
+ control: {
+ type: null,
+ },
+ description: 'Callback function to set field value.',
+ table: {
+ category: 'Events',
+ },
+ type: {
+ name: 'function',
+ required: true,
+ },
+ },
+ step: {
+ control: {
+ type: 'number',
+ },
+ description: 'Field incremental values that are valid.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'number',
+ required: false,
+ },
+ },
+ type: {
+ control: {
+ type: 'select',
+ },
+ description: 'Field type: input type or textarea.',
+ options: [
+ 'datetime-local',
+ 'email',
+ 'number',
+ 'search',
+ 'tel',
+ 'text',
+ 'textarea',
+ 'time',
+ 'url',
+ ],
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ value: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta<typeof LabelledFieldComponent>;
+
+const Template: ComponentStory<typeof LabelledFieldComponent> = (args) => (
+ <LabelledFieldComponent {...args} />
+);
+
+export const LabelledField = Template.bind({});
+LabelledField.args = {
+ id: 'labelled-field-storybook',
+ label: 'Labelled field',
+ name: 'labelled-field-storybook',
+ setValue: () => null,
+ value: '',
+};
diff --git a/src/components/molecules/forms/labelled-field.test.tsx b/src/components/molecules/forms/labelled-field.test.tsx
new file mode 100644
index 0000000..6fabe19
--- /dev/null
+++ b/src/components/molecules/forms/labelled-field.test.tsx
@@ -0,0 +1,19 @@
+import { render, screen } from '@test-utils';
+import LabelledField from './labelled-field';
+
+describe('LabelledField', () => {
+ it('renders a labelled field', () => {
+ render(
+ <LabelledField
+ type="text"
+ id="jest-text-field"
+ name="jest-text-field"
+ label="Jest text field"
+ value="test"
+ setValue={() => null}
+ />
+ );
+ expect(screen.getByLabelText('Jest text field')).toBeInTheDocument();
+ expect(screen.getByRole('textbox')).toHaveValue('test');
+ });
+});
diff --git a/src/components/molecules/forms/labelled-field.tsx b/src/components/molecules/forms/labelled-field.tsx
new file mode 100644
index 0000000..7f81e23
--- /dev/null
+++ b/src/components/molecules/forms/labelled-field.tsx
@@ -0,0 +1,25 @@
+import Field, { type FieldProps } from '@components/atoms/forms/field';
+import Label from '@components/atoms/forms/label';
+import { FC } from 'react';
+
+type LabelledFieldProps = FieldProps & {
+ label: string;
+};
+
+const LabelledField: FC<LabelledFieldProps> = ({
+ id,
+ label,
+ required,
+ ...props
+}) => {
+ return (
+ <>
+ <Label htmlFor={id} required={required}>
+ {label}
+ </Label>
+ <Field id={id} required={required} {...props} />
+ </>
+ );
+};
+
+export default LabelledField;