Skip to content

Commit 4f4d1ca

Browse files
committed
Added Molecules InputFormField
1 parent db8e585 commit 4f4d1ca

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export { ErrorBanner } from "./molecules/errors/error-banner";
6666
// Form Fields
6767

6868
export { CheckboxFormField } from "./molecules/form-fields/checkbox-form-field";
69+
export { InputFormField } from "./molecules/form-fields/input-form-field";
6970

7071
// #endregion Molecules
7172

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { InputFormField } from "./input-form-field";
2+
import { text, boolean, number, select } from "@storybook/addon-knobs";
3+
import React from "react";
4+
import { InputTypes } from "../../atoms/constants/input-types";
5+
6+
export default {
7+
component: InputFormField,
8+
title: "Molecules | Forms / Input Form Field",
9+
};
10+
11+
export const inputFormFieldKnobs = () => (
12+
<InputFormField
13+
disabled={boolean("Disabled", false)}
14+
errorMessage={text("Error Message", "")}
15+
isValid={boolean("Valid", true)}
16+
label={text("Label", "Label")}
17+
maxLength={number("Max Length", 30)}
18+
onChange={() => {}}
19+
placeholder={text("Placeholder", "Placeholder...")}
20+
required={boolean("Required", true)}
21+
type={select("Type", InputTypes, InputTypes.Text)}
22+
value={text("Value", "Input Value")}
23+
/>
24+
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react";
2+
import { render } from "@testing-library/react";
3+
import { InputFormField } from "./input-form-field";
4+
import faker from "faker";
5+
6+
describe("InputFormField", () => {
7+
test("when default props, renders input with label", () => {
8+
// Arrange
9+
const expected = faker.random.words();
10+
11+
// Act
12+
const { getByLabelText } = render(
13+
<InputFormField label={expected} onChange={() => {}} />
14+
);
15+
16+
// Assert
17+
expect(getByLabelText(expected)).not.toBeNull();
18+
});
19+
});
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React, { forwardRef, Ref, RefObject } from "react";
2+
import uuid from "uuid";
3+
import { CollectionUtils, StringUtils } from "andculturecode-javascript-core";
4+
import { InputCharacterCount } from "../../atoms/forms/input-character-count";
5+
import { InputTypes } from "../../atoms/constants/input-types";
6+
import { InputProperties } from "../../atoms/interfaces/input-properties";
7+
8+
// -----------------------------------------------------------------------------------------
9+
// #region Constants
10+
// -----------------------------------------------------------------------------------------
11+
12+
const COMPONENT_CLASS = "c-form-field";
13+
14+
// #endregion Constants
15+
16+
// -----------------------------------------------------------------------------------------
17+
// #region Interfaces
18+
// -----------------------------------------------------------------------------------------
19+
20+
export interface InputFormFieldProps extends InputProperties {
21+
errorMessage?: string;
22+
errorMessages?: string[];
23+
24+
/**
25+
* Unique identifier used select the underlying <input> for functional/e2e testing
26+
*/
27+
inputTestId?: string;
28+
29+
fieldId?: string;
30+
label: string;
31+
maxLength?: number;
32+
name?: string;
33+
ref?: RefObject<HTMLInputElement>;
34+
required?: boolean;
35+
showCharacterCount?: boolean;
36+
showLabelForScreenReadersOnly?: boolean;
37+
}
38+
39+
// #endregion Interfaces
40+
41+
// -----------------------------------------------------------------------------------------
42+
// #region Component
43+
// -----------------------------------------------------------------------------------------
44+
45+
const InputFormField: React.RefForwardingComponent<
46+
HTMLInputElement,
47+
InputFormFieldProps
48+
> = forwardRef((props: InputFormFieldProps, ref: Ref<HTMLInputElement>) => {
49+
const {
50+
disabled,
51+
errorMessage,
52+
errorMessages,
53+
inputTestId,
54+
isValid,
55+
label,
56+
maxLength,
57+
name,
58+
onChange,
59+
placeholder,
60+
required,
61+
showCharacterCount,
62+
showLabelForScreenReadersOnly,
63+
type,
64+
value,
65+
} = props;
66+
67+
const fieldId = props.fieldId ?? uuid.v4();
68+
69+
return (
70+
<div className={`${COMPONENT_CLASS} ${isValid ? "" : "-invalid"}`}>
71+
<label htmlFor={fieldId}>
72+
{showLabelForScreenReadersOnly ? (
73+
<span className="sr-only">{label}</span>
74+
) : (
75+
<React.Fragment>{label}</React.Fragment>
76+
)}
77+
{required && (
78+
<span className={`${COMPONENT_CLASS}__required`}>
79+
{" *"}
80+
</span>
81+
)}
82+
</label>
83+
<input
84+
data-test-id={inputTestId}
85+
disabled={disabled}
86+
id={fieldId}
87+
placeholder={placeholder}
88+
maxLength={maxLength ?? 20}
89+
name={name}
90+
onChange={onChange}
91+
ref={ref}
92+
type={type ?? InputTypes.Text}
93+
value={value}
94+
/>
95+
<div className={`${COMPONENT_CLASS}__bottom`}>
96+
<div className={`${COMPONENT_CLASS}__bottom__errors`}>
97+
{// if
98+
StringUtils.hasValue(errorMessage) && (
99+
<label>{errorMessage}</label>
100+
)}
101+
{// if
102+
CollectionUtils.hasValues(errorMessages) &&
103+
errorMessages?.map((s: string) => (
104+
<label key={s}>{s}</label>
105+
))}
106+
</div>
107+
{// if
108+
showCharacterCount !== false && (
109+
<InputCharacterCount
110+
currentLength={(value ?? "").length}
111+
maxLength={maxLength ?? 20}
112+
/>
113+
)}
114+
</div>
115+
</div>
116+
);
117+
});
118+
119+
// #endregion Component
120+
121+
// -----------------------------------------------------------------------------------------
122+
// #region Exports
123+
// -----------------------------------------------------------------------------------------
124+
125+
export { InputFormField };
126+
127+
// #endregion Exports

0 commit comments

Comments
 (0)