diff --git a/.circleci/config.yml b/.circleci/config.yml index b912533503..4bc7b6d8ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -133,7 +133,7 @@ parameters: default: "main" type: string sandbox_git_branch: # change to feature branch to test deployment - default: "js-117-selection-of-goals" + default: "js-6-frontend-validations" type: string jobs: build_and_lint: diff --git a/frontend/package.json b/frontend/package.json index 9e746df00d..422e0b5e12 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/react-fontawesome": "^0.1.11", + "@hookform/error-message": "^0.0.5", "@testing-library/jest-dom": "^4.2.4", "@trussworks/react-uswds": "^1.9.1", "@use-it/interval": "^1.0.0", @@ -21,7 +22,7 @@ "react-dom": "^16.14.0", "react-dropzone": "^11.2.0", "react-helmet": "^6.1.0", - "react-hook-form": "^6.9.0", + "react-hook-form": "^6.15.0", "react-idle-timer": "^4.4.2", "react-input-autosize": "^3.0.0", "react-responsive": "^8.1.1", diff --git a/frontend/src/components/DatePicker.css b/frontend/src/components/DatePicker.css index 6539117efa..1586359b58 100644 --- a/frontend/src/components/DatePicker.css +++ b/frontend/src/components/DatePicker.css @@ -14,3 +14,9 @@ .DateInput { width: fit-content; } + +.usa-hint { + font-size: 14px; + margin-top: 5px; + margin-bottom: 10px; +} \ No newline at end of file diff --git a/frontend/src/components/DatePicker.js b/frontend/src/components/DatePicker.js index beab959f6c..1863257cac 100644 --- a/frontend/src/components/DatePicker.js +++ b/frontend/src/components/DatePicker.js @@ -10,10 +10,8 @@ 2. react-dates had easily readable documentation and conveniences such as `maxDate` and `minDate`. I couldn't find great docs using the USWDS datepicker javascript */ - import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Label } from '@trussworks/react-uswds'; import { SingleDatePicker } from 'react-dates'; import { OPEN_UP, OPEN_DOWN } from 'react-dates/constants'; import { Controller } from 'react-hook-form'; @@ -24,9 +22,8 @@ import './DatePicker.css'; const dateFmt = 'MM/DD/YYYY'; const DateInput = ({ - control, label, minDate, name, disabled, maxDate, openUp, required, + control, minDate, name, disabled, maxDate, openUp, required, }) => { - const labelId = `${name}-id`; const hintId = `${name}-hint`; const [isFocused, updateFocus] = useState(false); const openDirection = openUp ? OPEN_UP : OPEN_DOWN; @@ -40,8 +37,7 @@ const DateInput = ({ return ( <> - -
mm/dd/yyyy
+
mm/dd/yyyy
{ const date = value ? moment(value, dateFmt) : null; @@ -57,7 +53,10 @@ const DateInput = ({ numberOfMonths={1} openDirection={openDirection} disabled={disabled} - onDateChange={(d) => { onChange(d.format(dateFmt)); }} + onDateChange={(d) => { + const newDate = d ? d.format(dateFmt) : d; + onChange(newDate); + }} onFocusChange={({ focused }) => updateFocus(focused)} /> @@ -68,7 +67,7 @@ const DateInput = ({ disabled={disabled} defaultValue={null} rules={{ - required, + required: required ? 'Please select a date' : false, }} /> @@ -80,7 +79,6 @@ DateInput.propTypes = { // eslint-disable-next-line react/forbid-prop-types control: PropTypes.object.isRequired, name: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, minDate: PropTypes.string, maxDate: PropTypes.string, openUp: PropTypes.bool, diff --git a/frontend/src/components/FormItem.css b/frontend/src/components/FormItem.css new file mode 100644 index 0000000000..f2ddc3bd1e --- /dev/null +++ b/frontend/src/components/FormItem.css @@ -0,0 +1,5 @@ +.smart-hub--form-required { + font-family: SourceSansPro; + font-size: 16px; + color: #d42240; +} diff --git a/frontend/src/components/FormItem.js b/frontend/src/components/FormItem.js new file mode 100644 index 0000000000..672d74268a --- /dev/null +++ b/frontend/src/components/FormItem.js @@ -0,0 +1,79 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useFormContext } from 'react-hook-form'; +import { ErrorMessage as ReactHookFormError } from '@hookform/error-message'; +import { + Label, FormGroup, ErrorMessage, Fieldset, +} from '@trussworks/react-uswds'; + +import './FormItem.css'; + +const labelPropTypes = { + label: PropTypes.node.isRequired, + children: PropTypes.node.isRequired, +}; + +function Checkbox({ label, children }) { + return ( +
+ {label} + {children} +
+ ); +} + +Checkbox.propTypes = labelPropTypes; + +function Field({ label, children }) { + return ( + + ); +} + +Field.propTypes = labelPropTypes; + +function FormItem({ + label, children, required, name, isCheckbox, +}) { + const { formState: { errors } } = useFormContext(); + const fieldErrors = errors[name]; + const labelWithRequiredTag = ( + <> + {label} + {required && ( (Required))} + + ); + + const LabelType = isCheckbox ? Checkbox : Field; + + return ( + + + {message}} + /> + {children} + + + ); +} + +FormItem.propTypes = { + label: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + name: PropTypes.string.isRequired, + isCheckbox: PropTypes.bool, + required: PropTypes.bool, +}; + +FormItem.defaultProps = { + required: true, + isCheckbox: false, +}; + +export default FormItem; diff --git a/frontend/src/components/MultiSelect.js b/frontend/src/components/MultiSelect.js index 68c79d3717..ce008e2c47 100644 --- a/frontend/src/components/MultiSelect.js +++ b/frontend/src/components/MultiSelect.js @@ -23,7 +23,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select, { components } from 'react-select'; -import { Label } from '@trussworks/react-uswds'; import { Controller } from 'react-hook-form'; import arrowBoth from '../images/arrow-both.svg'; @@ -73,7 +72,6 @@ const styles = { }; function MultiSelect({ - label, name, options, disabled, @@ -117,44 +115,45 @@ function MultiSelect({ }; return ( -