Skip to content

Commit

Permalink
OPHJOD-294: Add input field component
Browse files Browse the repository at this point in the history
  • Loading branch information
sauanto committed Apr 11, 2024
1 parent b37baca commit 2fd2cd0
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 1 deletion.
116 changes: 116 additions & 0 deletions lib/components/InputField/InputField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { useState } from 'react';

import { InputField } from './InputField';

const meta = {
title: 'Forms/InputField',
component: InputField,
tags: ['autodocs'],
} satisfies Meta<typeof InputField>;

export default meta;

type Story = StoryObj<typeof meta>;

const render = (args: Story['args']) => {
const { value, onChange, ...rest } = args;
const [textValue, setTextValue] = useState(value);
return (
<InputField
value={textValue}
onChange={(newValue) => {
setTextValue(newValue);
onChange(newValue);
}}
{...rest}
/>
);
};
const url = 'https://www.figma.com/file/6M2LrpSCcB0thlFDaQAI2J/cx_jod_client?node-id=542%3A7550';
const label = 'Label text';
const loremIpsum = 'Lorem ipsum dolor sit amet';

export const Default: Story = {
render,
decorators: [
(Story) => (
<div className="max-w-[415px]">
<Story />
</div>
),
],
parameters: {
design: {
type: 'figma',
url,
},
docs: {
description: {
story: 'This is a input field component for displaying and editing a value with a label text.',
},
},
},
args: {
value: loremIpsum,
onChange: fn(),
label,
},
};

export const HelpText: Story = {
render,
decorators: [
(Story) => (
<div className="max-w-[415px]">
<Story />
</div>
),
],
parameters: {
design: {
type: 'figma',
url,
},
docs: {
description: {
story: 'This is a input field component for displaying and editing a value with a label and help text.',
},
},
},
args: {
value: loremIpsum,
onChange: fn(),
label,
help: 'Help text',
},
};

export const Placeholder: Story = {
render,
decorators: [
(Story) => (
<div className="max-w-[415px]">
<Story />
</div>
),
],
parameters: {
design: {
type: 'figma',
url,
},
docs: {
description: {
story: 'This is a input field component for displaying and editing a value with placeholder text.',
},
},
},
args: {
value: '',
onChange: fn(),
label,
placeholder: loremIpsum,
},
};
50 changes: 50 additions & 0 deletions lib/components/InputField/InputField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';

import { InputField } from './InputField';

describe('InputField', () => {
const label = 'Username';

it('renders label correctly', () => {
const value = 'john';
const onChange = vi.fn();
const { container } = render(<InputField value={value} onChange={onChange} label={label} />);
const labelElement = screen.getByText(label);
expect(labelElement).toBeInTheDocument();
expect(container.firstChild).toMatchSnapshot();
});

it('renders input correctly', () => {
const placeholder = 'Enter your username';
const value = 'john';
const onChange = vi.fn();
const { container } = render(
<InputField value={value} onChange={onChange} label={label} placeholder={placeholder} />,
);
const inputElement = screen.getByPlaceholderText(`(${placeholder})`);
expect(inputElement).toBeInTheDocument();
expect(container.firstChild).toMatchSnapshot();
});

it('renders help text correctly', () => {
const helpText = 'Please enter your username';
const value = 'john';
const onChange = vi.fn();
const { container } = render(<InputField value={value} onChange={onChange} label={label} help={helpText} />);
const helpElement = screen.getByText(helpText);
expect(helpElement).toBeInTheDocument();
expect(container.firstChild).toMatchSnapshot();
});

it('updates input value correctly', () => {
const onChange = vi.fn();
const value = 'test value';
const { container } = render(<InputField value={value} onChange={onChange} label={label} />);
const inputElement = screen.getByRole('textbox');
fireEvent.change(inputElement, { target: { value: 'new value' } });
expect(onChange).toHaveBeenCalledWith('new value');
expect(container.firstChild).toMatchSnapshot();
});
});
36 changes: 36 additions & 0 deletions lib/components/InputField/InputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useId } from 'react';

export interface InputFieldProps {
value: string;
onChange: (newValue: string) => void;
placeholder?: string;
label: string;
help?: string;
}

export const InputField = ({ value, onChange, placeholder, label, help }: InputFieldProps) => {
const inputId = useId();
const helpId = useId();
return (
<>
<label htmlFor={inputId} className="mb-4 inline-block align-top text-form-label text-primary-gray">
{label}
</label>
<input
id={inputId}
type="text"
value={value}
onChange={(event) => onChange(event.target.value)}
placeholder={placeholder ? `(${placeholder})` : undefined}
autoComplete="off"
aria-describedby={help ? helpId : undefined}
className="block w-full rounded-[10px] border-[5px] border-border-gray bg-white p-[11px] text-primary-gray outline-none placeholder:text-secondary-gray"
/>
{help && (
<div id={helpId} className="mt-2 block text-help text-secondary-3">
{help}
</div>
)}
</>
);
};
37 changes: 37 additions & 0 deletions lib/components/InputField/__snapshots__/InputField.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`InputField > renders help text correctly 1`] = `
<label
class="mb-4 inline-block align-top text-form-label text-primary-gray"
for=":r4:"
>
Username
</label>
`;

exports[`InputField > renders input correctly 1`] = `
<label
class="mb-4 inline-block align-top text-form-label text-primary-gray"
for=":r2:"
>
Username
</label>
`;

exports[`InputField > renders label correctly 1`] = `
<label
class="mb-4 inline-block align-top text-form-label text-primary-gray"
for=":r0:"
>
Username
</label>
`;

exports[`InputField > updates input value correctly 1`] = `
<label
class="mb-4 inline-block align-top text-form-label text-primary-gray"
for=":r6:"
>
Username
</label>
`;
5 changes: 4 additions & 1 deletion lib/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import './index.css';

export { Button } from './components/Button/Button';
export { HeroCard } from './components/HeroCard/HeroCard';
export { DropdownMenu } from './components/DropdownMenu/DropdownMenu';
export { Expander } from './components/Expander/Expander';
export { HeroCard } from './components/HeroCard/HeroCard';
export { InputField } from './components/InputField/InputField';
export { NavigationBar } from './components/NavigationBar/NavigationBar';
export { Note } from './components/Note/Note';
export { RadioButton } from './components/RadioButton/RadioButton';
export { RadioButtonGroup } from './components/RadioButton/RadioButtonGroup';
export { ResultsCard } from './components/ResultsCard/ResultsCard';
export { RoundButton } from './components/RoundButton/RoundButton';
export { RoundLinkButton } from './components/RoundLinkButton/RoundLinkButton';
export { Toast } from './components/Toast/Toast';

0 comments on commit 2fd2cd0

Please sign in to comment.