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

feat: Support generic validation messages on inputs. #110

Merged
merged 20 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from 15 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
43 changes: 39 additions & 4 deletions packages/components/src/FormField/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classnames from "classnames";
import uuid from "uuid";
import { Icon } from "../Icon";
import { Text } from "../Text";
import { InputValidation, ValidationProps } from "../InputValidation";
import styles from "./FormField.css";

export interface FormFieldProps {
Expand Down Expand Up @@ -30,15 +31,15 @@ export interface FormFieldProps {
readonly disabled?: boolean;

/**
* **EXPERIMENTAL** This feature is still under development.
* **DEPRECATED** Use `validations` prop instead.
*
* Show an error message and highlight the the field red.
*/
readonly errorMessage?: string;

/**
* Adjusts the form field to go inline with a content. This also silences the
* given `errorMessage` prop. You'd have to used the `onValidate` prop to
* given `validations` prop. You'd have to used the `onValidate` prop to
* capture the message and render it somewhere else using the `Text` component.
darryltec marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly inline?: boolean;
Expand Down Expand Up @@ -96,6 +97,14 @@ export interface FormFieldProps {
*/
readonly type?: "text" | "number" | "time" | "textarea" | "select";

/**
* **EXPERIMENTAL** This feature is still under development.
*
* Show a success, error, warn, and info message above the field. This also
* highlights the the field red if and error message shows up.
*/
readonly validations?: ValidationProps[];

/**
* Set the component to the given value.
*/
Expand All @@ -118,13 +127,21 @@ export interface FormFieldProps {
onBlur?(): void;

/**
* **EXPERIMENTAL** This feature is still under development.
* **DEPRECATED** Use `onValidation` prop instead.
seanhealy marked this conversation as resolved.
Show resolved Hide resolved
*
* Callback to get the the status and message when validating a field
* @param status
* @param message
*/
onValidate?(status: "pass" | "fail", message: string): void;

/**
* **EXPERIMENTAL** This feature is still under development.
*
* Callback to get the the status and message when validating a field
* @param messages
*/
onValidation?(messages: ValidationProps[]): void;
}

export const FormField = React.forwardRef(
Expand All @@ -145,12 +162,14 @@ export const FormField = React.forwardRef(
onBlur,
onChange,
onValidate,
onValidation,
placeholder,
readonly,
rows,
size,
type = "text",
value,
validations,
}: FormFieldProps,
ref:
| Ref<HTMLInputElement>
Expand All @@ -166,13 +185,24 @@ export const FormField = React.forwardRef(
handleValidation();
}, [value]);

let hasErrors = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just thinking we could make a little function that would return hasErrors this would let us do something like const hasErrors = areThereMessages(validations) here which would simplify reading this component and then isolate this logic into a method. Also, it's an excuse to use VSCode's fancy refactor tools!

if (validations) {
hasErrors =
validations.filter(validation => {
darryltec marked this conversation as resolved.
Show resolved Hide resolved
return (
(validation.shouldShow || validation.shouldShow === undefined) &&
validation.status === "error"
);
}).length > 0;
}

const wrapperClassNames = classnames(
styles.wrapper,
inline && styles.inline,
size && styles[size],
align && styles[align],
errorMessage && styles.hasErrorMessage,
(invalid || errorMessage) && styles.invalid,
(invalid || errorMessage || hasErrors) && styles.invalid,
disabled && styles.disabled,
maxLength && styles.maxLength,
{
Expand All @@ -189,6 +219,8 @@ export const FormField = React.forwardRef(
<Text variation="error">{errorMessage}</Text>
)}

{validations && !inline && <InputValidation messages={validations} />}

<Wrapper
className={wrapperClassNames}
style={{ ["--formField-maxLength" as string]: maxLength || max }}
Expand Down Expand Up @@ -282,6 +314,9 @@ export const FormField = React.forwardRef(
const status = errorMessage ? "fail" : "pass";
const message = errorMessage || "";
onValidate && onValidate(status, message);

const validationMessages = validations ? validations : [];
onValidation && onValidation(validationMessages);
}
},
);
43 changes: 0 additions & 43 deletions packages/components/src/InputNumber/InputNumber.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -110,49 +110,6 @@ Input number is used in forms that accept numbers as an answer.
</Text>
</Playground>

## Using onValidate

If you need to capture the error message and render it outside of the component.
darryltec marked this conversation as resolved.
Show resolved Hide resolved
You can use `onValidate` to catch them.

<Playground>
{() => {
const [value, setValue] = useState(5);
const [validationMessage, setValidationMessage] = useState("");
return (
<>
{validationMessage && <Text variation="error">{validationMessage}</Text>}
<Text>
Follow-up after
<InputNumber
name="durationInDays"
value={value}
size="small"
inline={true}
max={4}
min={0}
onChange={handleChange}
onValidate={handleValidation}
align="center"
/>
days
</Text>
</>
);

function handleChange(newValue) {
setValue(newValue);
};

function handleValidation(status, message) {
console.log(status, message)
setValidationMessage(message);
};

}}

</Playground>

## Properties

<Props of={InputNumber} />
39 changes: 39 additions & 0 deletions packages/components/src/InputText/InputText.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,45 @@ Use this to allow users to provide long answers.
/>
</Playground>

## Validation message

You can add your own custom validation messages on a field. However, this
shouldn't replace of your server-side validation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
shouldn't replace of your server-side validation.
shouldn't replace server-side validation.


<Playground>
{() => {
const [value, setValue] = useState("");
return (
<InputText
value={value}
onChange={handleChange}
name="age"
placeholder="What's your age"
validations={[
{
shouldShow: value.length > 2 && value.length < 10 && isNaN(value),
message: "Now that's a word, keep going!",
status: "success"
},
{
shouldShow: value.length >= 10,
message: "You sure you're that old?",
status: "warn"
},
{
shouldShow: value.length > 0 && !isNaN(value),
message: "Type your age in words",
status: "error"
}
]}
/>
);
function handleChange(newValue) {
setValue(newValue);
}
}}
</Playground>

## States

### Disabled
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/InputValidation/InputValidation.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.hasValidationMessage {
margin-bottom: var(--space-smaller);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const hasValidationMessage: string;
56 changes: 56 additions & 0 deletions packages/components/src/InputValidation/InputValidation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: InputValidation
menu: Components
route: /components/input-validation
---

import { Playground, Props } from "docz";
import { ComponentStatus } from "@jobber/docx";
import { InputValidation } from ".";
import { Text } from "../Text";
import { InputText } from "../InputText";
import { useState } from "react";

# Input Validation

<ComponentStatus stage="pre" responsive="yes" accessible="yes" />

This component allows you to show the validation messages outside of the inputs.

<Playground>
{() => {
const [value, setValue] = useState("Juan");
const [validationMessage, setValidationMessage] = useState(undefined);
darryltec marked this conversation as resolved.
Show resolved Hide resolved
return (
<>
<InputValidation messages={validationMessage} />
<Text>
My name is
<InputText
validations={[
{
shouldShow: value !== "Jeff",
message: "Your name is supposed to be Jeff",
status: "error",
},
]}
onValidation={setValidationMessage}
name="myName"
value={value}
size="small"
inline={true}
onChange={setValue}
align="center"
maxLength={4}
/>
</Text>
</>
);

}}

</Playground>

## InputValidation Properties

<Props of={InputValidation} />
Loading