aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/forms/fields/select
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/atoms/forms/fields/select')
-rw-r--r--src/components/atoms/forms/fields/select/index.ts1
-rw-r--r--src/components/atoms/forms/fields/select/select.stories.tsx143
-rw-r--r--src/components/atoms/forms/fields/select/select.test.tsx43
-rw-r--r--src/components/atoms/forms/fields/select/select.tsx76
4 files changed, 263 insertions, 0 deletions
diff --git a/src/components/atoms/forms/fields/select/index.ts b/src/components/atoms/forms/fields/select/index.ts
new file mode 100644
index 0000000..c739673
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/index.ts
@@ -0,0 +1 @@
+export * from './select';
diff --git a/src/components/atoms/forms/fields/select/select.stories.tsx b/src/components/atoms/forms/fields/select/select.stories.tsx
new file mode 100644
index 0000000..c9e02d2
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/select.stories.tsx
@@ -0,0 +1,143 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { ChangeEvent, useCallback, useState } from 'react';
+import { Select as SelectComponent } from './select';
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+
+/**
+ * Select - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms/Fields',
+ component: SelectComponent,
+ args: {
+ isDisabled: false,
+ isRequired: false,
+ },
+ argTypes: {
+ 'aria-labelledby': {
+ control: {
+ type: 'text',
+ },
+ description: 'One or more ids that refers to the select field name.',
+ table: {
+ category: 'Accessibility',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add classnames to the select field.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isDisabled: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Field state: either enabled or disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ isRequired: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Determine if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ options: {
+ description: 'Select options.',
+ type: {
+ name: 'array',
+ required: true,
+ value: {
+ name: 'string',
+ },
+ },
+ },
+ value: {
+ control: {
+ type: null,
+ },
+ description: 'Field value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta<typeof SelectComponent>;
+
+const Template: ComponentStory<typeof SelectComponent> = ({
+ onChange: _onChange,
+ value,
+ ...args
+}) => {
+ const [selected, setSelected] = useState(value);
+ const updateSelection = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
+ setSelected(e.target.value);
+ }, []);
+
+ return (
+ <SelectComponent {...args} onChange={updateSelection} value={selected} />
+ );
+};
+
+/**
+ * Select Story
+ */
+export const Select = Template.bind({});
+Select.args = {
+ id: 'storybook-select',
+ name: 'storybook-select',
+ options: selectOptions,
+ value: 'option2',
+};
diff --git a/src/components/atoms/forms/fields/select/select.test.tsx b/src/components/atoms/forms/fields/select/select.test.tsx
new file mode 100644
index 0000000..088cc9e
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/select.test.tsx
@@ -0,0 +1,43 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { Select } from './select';
+
+const doNothing = () => {
+ // do nothing
+};
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+const selected = selectOptions[0];
+
+describe('Select', () => {
+ it('should correctly set default option', () => {
+ render(
+ <Select
+ id="select-1"
+ name="select-1"
+ onChange={doNothing}
+ options={selectOptions}
+ value={selected.value}
+ />
+ );
+
+ expect(screen.getByRole('combobox')).toHaveValue(selected.value);
+ });
+
+ it('renders the select options', () => {
+ render(
+ <Select
+ id="select-2"
+ name="select-2"
+ onChange={doNothing}
+ options={selectOptions}
+ value={selected.value}
+ />
+ );
+
+ expect(screen.getAllByRole('option')).toHaveLength(selectOptions.length);
+ });
+});
diff --git a/src/components/atoms/forms/fields/select/select.tsx b/src/components/atoms/forms/fields/select/select.tsx
new file mode 100644
index 0000000..887dacc
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/select.tsx
@@ -0,0 +1,76 @@
+import { FC, SelectHTMLAttributes } from 'react';
+import styles from '../fields.module.scss';
+
+export type SelectOptions = {
+ /**
+ * The option id.
+ */
+ id: string;
+ /**
+ * The option name.
+ */
+ name: string;
+ /**
+ * The option value.
+ */
+ value: string;
+};
+
+export type SelectProps = Omit<
+ SelectHTMLAttributes<HTMLSelectElement>,
+ 'disabled' | 'hidden' | 'required'
+> & {
+ /**
+ * Should the select field be disabled?
+ *
+ * @default false
+ */
+ isDisabled?: boolean;
+ /**
+ * Should the select field be hidden?
+ *
+ * @default false
+ */
+ isHidden?: boolean;
+ /**
+ * Is the select field required?
+ *
+ * @default false
+ */
+ isRequired?: boolean;
+ /**
+ * True if the field is required. Default: false.
+ */
+ options: SelectOptions[];
+};
+
+/**
+ * Select component
+ *
+ * Render a HTML select element.
+ */
+export const Select: FC<SelectProps> = ({
+ className = '',
+ isDisabled = false,
+ isHidden = false,
+ isRequired = false,
+ options,
+ ...props
+}) => {
+ const selectClass = `${styles.field} ${styles['field--select']} ${className}`;
+
+ return (
+ <select
+ {...props}
+ className={selectClass}
+ disabled={isDisabled}
+ required={isRequired}
+ >
+ {options.map((option) => (
+ <option key={option.id} id={option.id} value={option.value}>
+ {option.name}
+ </option>
+ ))}
+ </select>
+ );
+};