-
+
-
+
- {/* TODO: the title on the page should be an h1 tag, not h4 */}
-
{props.title}
-
- {props.children}
+ {props.title}
+ {props.children}
+
- >
+
+
)
}
diff --git a/src/lib/content-layout/index.ts b/src/lib/content-layout/index.ts
index e0413bf1a..3125caebc 100644
--- a/src/lib/content-layout/index.ts
+++ b/src/lib/content-layout/index.ts
@@ -1,4 +1 @@
export { default as ContentLayout } from './ContentLayout'
-export
- // tslint:disable-next-line: no-unused-expression
-type { SectionSelectorProps } from './sections'
diff --git a/src/lib/content-layout/sections/Sections.module.scss b/src/lib/content-layout/sections/Sections.module.scss
deleted file mode 100644
index ad45ae9c7..000000000
--- a/src/lib/content-layout/sections/Sections.module.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@import '../../styles/';
-
-.sections {
- @include content-height;
-
- @include ltemd {
- display: none;
- }
-}
diff --git a/src/lib/content-layout/sections/Sections.test.tsx b/src/lib/content-layout/sections/Sections.test.tsx
deleted file mode 100644
index 3606036db..000000000
--- a/src/lib/content-layout/sections/Sections.test.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import '@testing-library/jest-dom'
-
-describe('
has at least one section', () => {
-
- test('it should render the sections panel', () => {})
-})
-
-describe('
has zero sections', () => {
-
- test('it should NOT render the content', () => { })
-})
diff --git a/src/lib/content-layout/sections/Sections.tsx b/src/lib/content-layout/sections/Sections.tsx
deleted file mode 100644
index 4899bf734..000000000
--- a/src/lib/content-layout/sections/Sections.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { FC } from 'react'
-
-import SectionSelector from './section-selector/Section-Selector'
-import { SectionsProps } from './sections-props.model'
-import styles from './Sections.module.scss'
-
-const Sections: FC
= (props: SectionsProps) => {
-
- // if we don't have any sections, hide the entire area
- if (!props.sections.length) {
- return <>>
- }
-
- const sections: Array = props.sections
- .map(section => (
-
- ))
-
- return (
-
- {sections}
-
- )
-}
-
-export default Sections
diff --git a/src/lib/content-layout/sections/index.ts b/src/lib/content-layout/sections/index.ts
deleted file mode 100644
index e4669eb62..000000000
--- a/src/lib/content-layout/sections/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as Sections } from './Sections'
-export * from './section-selector'
-export * from './sections-props.model'
diff --git a/src/lib/content-layout/sections/section-selector/Section-Selector.module.scss b/src/lib/content-layout/sections/section-selector/Section-Selector.module.scss
deleted file mode 100644
index 62e0df815..000000000
--- a/src/lib/content-layout/sections/section-selector/Section-Selector.module.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-@import '../../../styles/';
-
-$svg-size: $pad-xxxxl;
-
-.section-selector {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- height: calc(2 * $svg-size);
-
- &.active {
-
- svg {
- path {
- stroke: $turq-120;
- }
- }
-
- .title {
- color: $turq-120;
- }
- }
-
- svg {
- @include icon-xxxxl;
- fill: none;
-
- path {
- fill: none;
- stroke: $black-60;
- stroke-width: $border;
- }
- }
-
- .title {
- text-align: center;
- color: $black-60;
- // extend ultra small and override it
- @extend .ultra-small;
- font-style: normal;
- @include font-weight-semi-bold;
- line-height: 14px;
- text-transform: uppercase;
- }
-}
\ No newline at end of file
diff --git a/src/lib/content-layout/sections/section-selector/Section-Selector.test.tsx b/src/lib/content-layout/sections/section-selector/Section-Selector.test.tsx
deleted file mode 100644
index bbda6bb14..000000000
--- a/src/lib/content-layout/sections/section-selector/Section-Selector.test.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import '@testing-library/jest-dom'
-
-describe('', () => {
-
- test('it should render the section selector', () => {})
-})
diff --git a/src/lib/content-layout/sections/section-selector/Section-Selector.tsx b/src/lib/content-layout/sections/section-selector/Section-Selector.tsx
deleted file mode 100644
index aa36da3e9..000000000
--- a/src/lib/content-layout/sections/section-selector/Section-Selector.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import classNames from 'classnames'
-import { FC, SVGProps } from 'react'
-import { Link, useLocation } from 'react-router-dom'
-
-import { routeIsActive } from '../../../route-provider/route.utils' // cannot be imported from barrel
-
-import { SectionSelectorProps } from './section-selector-props.model'
-import styles from './Section-Selector.module.scss'
-
-const SectionSelector: FC = (props: SectionSelectorProps) => {
-
- const sectionRoute: string = `${props.toolRoute}${props.sectionRoute.route ? '/' : ''}${props.sectionRoute.route}`
- const isActive: boolean = routeIsActive(useLocation().pathname, sectionRoute, props.toolRoute)
- const Icon: FC> | undefined = props.sectionRoute.icon
-
- return (
-
-
- {Icon &&
}
-
- {props.sectionRoute.title}
-
-
-
- )
-}
-
-export default SectionSelector
diff --git a/src/lib/content-layout/sections/section-selector/index.ts b/src/lib/content-layout/sections/section-selector/index.ts
deleted file mode 100644
index cd9f9aa69..000000000
--- a/src/lib/content-layout/sections/section-selector/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as SectionSelector } from './Section-Selector'
-export * from './section-selector-props.model'
diff --git a/src/lib/content-layout/sections/section-selector/section-selector-props.model.ts b/src/lib/content-layout/sections/section-selector/section-selector-props.model.ts
deleted file mode 100644
index 9543f3895..000000000
--- a/src/lib/content-layout/sections/section-selector/section-selector-props.model.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { PlatformRoute } from '../../../route-provider'
-
-export interface SectionSelectorProps {
- sectionRoute: PlatformRoute
- toolRoute: string
-}
diff --git a/src/lib/content-layout/sections/sections-props.model.ts b/src/lib/content-layout/sections/sections-props.model.ts
deleted file mode 100644
index db7309c76..000000000
--- a/src/lib/content-layout/sections/sections-props.model.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { SectionSelectorProps } from './section-selector/section-selector-props.model'
-
-export interface SectionsProps {
- sections: Array
-}
diff --git a/src/lib/form/Form.module.scss b/src/lib/form/Form.module.scss
index e9f39b880..50467e712 100644
--- a/src/lib/form/Form.module.scss
+++ b/src/lib/form/Form.module.scss
@@ -1,11 +1,12 @@
@import '../../lib/styles';
-.form-fields {
- display: grid;
- grid-template-columns: .5fr;
- justify-content: flex-end;
+form {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
- @include ltemd {
+ .form-fields {
+ display: grid;
grid-template-columns: 1fr;
justify-content: center;
}
diff --git a/src/lib/form/Form.tsx b/src/lib/form/Form.tsx
index 0c56e3b94..0ad33b689 100644
--- a/src/lib/form/Form.tsx
+++ b/src/lib/form/Form.tsx
@@ -8,9 +8,9 @@ import {
FormErrorMessage,
formGetInputModel,
formInitializeValues,
+ formOnChange,
formReset,
formSubmitAsync,
- formValidateAndUpdateAsync,
} from './form-functions'
import { InputText, InputTextarea } from './form-input'
import { FormInputModel } from './form-input.model'
@@ -22,13 +22,14 @@ interface FormProps {
readonly requestGenerator: (inputs: ReadonlyArray) => RequestType
readonly resetOnError: boolean
readonly save: (value: RequestType) => Promise
+ readonly succeeded?: () => void
}
const Form: (props: FormProps) => JSX.Element
= (props: FormProps) => {
const [disableSave, setDisableSave]: [boolean, Dispatch>]
- = useState(true)
+ = useState(false)
const [formDef, setFormDef]: [FormDefinition, Dispatch>]
= useState({ ...props.formDef })
@@ -48,14 +49,14 @@ const Form: (props: FormProps): Promise {
- const isValid: boolean = await formValidateAndUpdateAsync(event, formDef.inputs)
+ const isValid: boolean = await formOnChange(event, formDef.inputs)
setFormDef({ ...formDef })
setDisableSave(!isValid)
}
function onFocus(event: FocusEvent): void {
const inputDef: FormInputModel = formGetInputModel(props.formDef.inputs, event.target.name)
- inputDef.dirtyOrTouched = true
+ inputDef.touched = true
setFormDef({ ...formDef })
}
@@ -67,7 +68,7 @@ const Form: (props: FormProps): Promise {
const values: RequestType = props.requestGenerator(formDef.inputs)
- formSubmitAsync(event, formDef.inputs, props.formDef.title, values, props.save, setDisableSave)
+ formSubmitAsync(event, formDef.inputs, props.formDef.title, values, props.save, setDisableSave, props.succeeded)
.then(() => {
setFormKey(Date.now())
formReset(formDef.inputs, props.formValues)
@@ -85,9 +86,10 @@ const Form: (props: FormProps = props.formDef.inputs
- .map(input => formGetInputModel(props.formDef.inputs, input.name))
+ const formInputs: Array = formDef.inputs
+ .map(input => formGetInputModel(formDef.inputs, input.name))
.map((inputModel, index) => {
+ const tabIndex: number = inputModel.notTabbable ? -1 : index + 1 + (formDef.tabIndexStart || 0)
switch (inputModel.type) {
case 'textarea':
return (
@@ -96,7 +98,7 @@ const Form: (props: FormProps
)
@@ -107,7 +109,7 @@ const Form: (props: FormProps
@@ -116,7 +118,7 @@ const Form: (props: FormProps = props.formDef.buttons
+ const buttons: Array = formDef.buttons
.map((button, index) => {
// if this is a reset button, set its onclick to reset
if (!!button.isReset) {
@@ -130,7 +132,7 @@ const Form: (props: FormProps
)
})
@@ -143,14 +145,18 @@ const Form: (props: FormProps
- {props.formDef.title}
+ {props.formDef.title}
+
+
{formInputs}
-
- {buttons}
+
diff --git a/src/lib/form/form-button.model.ts b/src/lib/form/form-button.model.ts
index 0a36104b5..49918ca7e 100644
--- a/src/lib/form/form-button.model.ts
+++ b/src/lib/form/form-button.model.ts
@@ -7,7 +7,7 @@ export interface FormButton {
readonly label: string
readonly notTabble?: boolean
readonly onClick?: (event?: any) => void
- route?: string
+ readonly route?: string
readonly size?: ButtonSize
readonly type?: ButtonType
readonly url?: string
diff --git a/src/lib/form/form-definition.model.ts b/src/lib/form/form-definition.model.ts
index 101502547..79eff6228 100644
--- a/src/lib/form/form-definition.model.ts
+++ b/src/lib/form/form-definition.model.ts
@@ -4,5 +4,6 @@ import { FormInputModel } from './form-input.model'
export interface FormDefinition {
readonly buttons: ReadonlyArray
readonly inputs: ReadonlyArray
+ readonly tabIndexStart?: number
readonly title: string
}
diff --git a/src/lib/form/form-functions/form.functions.ts b/src/lib/form/form-functions/form.functions.ts
index 3f701c30e..5f8f9ba1a 100644
--- a/src/lib/form/form-functions/form.functions.ts
+++ b/src/lib/form/form-functions/form.functions.ts
@@ -26,7 +26,7 @@ export function getInputModel(inputs: ReadonlyArray, fieldName:
export function initializeValues(inputs: ReadonlyArray, formValues?: T): void {
inputs
- .filter(input => !input.dirtyOrTouched)
+ .filter(input => !input.dirty && !input.touched)
.forEach(input => {
input.value = !!(formValues as any)?.hasOwnProperty(input.name)
? (formValues as any)[input.name]
@@ -34,10 +34,26 @@ export function initializeValues(inputs: ReadonlyArray, formV
})
}
+export function onChange(event: FormEvent, inputs: ReadonlyArray): boolean {
+
+ const input: HTMLInputElement = (event.target as HTMLInputElement)
+ // set the input def info
+ const inputDef: FormInputModel = getInputModel(inputs, input.name)
+ inputDef.dirty = true
+ inputDef.value = input.value
+
+ // validate the form
+ const formElements: HTMLFormControlsCollection = (input.form as HTMLFormElement).elements
+ const isValid: boolean = validate(inputs, formElements)
+
+ return isValid
+}
+
export function reset(inputs: ReadonlyArray, formValue?: any): void {
inputs
.forEach(inputDef => {
- inputDef.dirtyOrTouched = false
+ inputDef.dirty = false
+ inputDef.touched = false
inputDef.error = undefined
inputDef.value = formValue?.[inputDef.name]
})
@@ -50,25 +66,25 @@ export async function submitAsync(
formValue: T,
save: (value: T) => Promise,
setDisableButton: Dispatch>,
+ succeeded?: () => void,
): Promise {
event.preventDefault()
setDisableButton(true)
- // if there are no dirty fields, display a message and stop submitting
- const dirty: FormInputModel | undefined = inputs.find(fieldDef => !!fieldDef.dirtyOrTouched)
+ // if there are no dirty fields, just run the succeeded method
+ const dirty: FormInputModel | undefined = inputs.find(fieldDef => !!fieldDef.dirty)
if (!dirty) {
- toast.info('No changes detected.')
- return
+ succeeded?.()
+ return Promise.resolve()
}
// get the form values so we can validate them
const formValues: HTMLFormControlsCollection = (event.target as HTMLFormElement).elements
// if there are any validation errors, display a message and stop submitting
- const isValid: boolean = await validateAsync(inputs, formValues, true)
+ const isValid: boolean = validate(inputs, formValues, true)
if (!isValid) {
- toast.error('Changes could not be saved. Please resolve errors.')
return Promise.reject(ErrorMessage.submit)
}
@@ -85,30 +101,15 @@ export async function submitAsync(
})
}
-export async function validateAndUpdateAsync(event: FormEvent, inputs: ReadonlyArray): Promise {
-
- const input: HTMLInputElement = (event.target as HTMLInputElement)
- // set the input def info
- const inputDef: FormInputModel = getInputModel(inputs, input.name)
- inputDef.dirtyOrTouched = true
- inputDef.value = input.value
-
- // validate the form
- const formElements: HTMLFormControlsCollection = (input.form as HTMLFormElement).elements
- const isValid: boolean = await validateAsync(inputs, formElements)
-
- return isValid
-}
-
-async function validateAsync(inputs: ReadonlyArray, formElements: HTMLFormControlsCollection, formDirty?: boolean): Promise {
+function validate(inputs: ReadonlyArray, formElements: HTMLFormControlsCollection, formDirty?: boolean): boolean {
const errors: ReadonlyArray = inputs
.filter(formInputDef => {
formInputDef.error = undefined
- formInputDef.dirtyOrTouched = formInputDef.dirtyOrTouched || !!formDirty
+ formInputDef.dirty = formInputDef.dirty || !!formDirty
formInputDef.validateOnChange
- ?.forEach(async validator => {
+ ?.forEach(validator => {
if (!formInputDef.error) {
- formInputDef.error = await validator(formInputDef.value, formElements, formInputDef.dependentField)
+ formInputDef.error = validator(formInputDef.value, formElements, formInputDef.dependentField)
}
})
return !!formInputDef.error
diff --git a/src/lib/form/form-functions/index.ts b/src/lib/form/form-functions/index.ts
index fbce4c278..f0979b852 100644
--- a/src/lib/form/form-functions/index.ts
+++ b/src/lib/form/form-functions/index.ts
@@ -3,7 +3,7 @@ export {
getInputElement as formGetInput,
getInputModel as formGetInputModel,
initializeValues as formInitializeValues,
+ onChange as formOnChange,
reset as formReset,
submitAsync as formSubmitAsync,
- validateAndUpdateAsync as formValidateAndUpdateAsync,
} from './form.functions'
diff --git a/src/lib/form/form-input.model.ts b/src/lib/form/form-input.model.ts
index 2d4c091c5..7e9836b8e 100644
--- a/src/lib/form/form-input.model.ts
+++ b/src/lib/form/form-input.model.ts
@@ -2,7 +2,7 @@ import { ValidatorFn } from './validator-functions'
export interface FormInputModel {
readonly dependentField?: string
- dirtyOrTouched?: boolean
+ dirty?: boolean
disabled?: boolean
error?: string
readonly hint?: string
@@ -11,6 +11,7 @@ export interface FormInputModel {
readonly notTabbable?: boolean
readonly placeholder?: string
readonly preventAutocomplete?: boolean
+ touched?: boolean
readonly type: 'password' | 'text' | 'textarea'
readonly validateOnBlur?: ValidatorFn
readonly validateOnChange?: ValidatorFn
diff --git a/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.module.scss b/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.module.scss
index 9cfc7d4e2..89c5af82f 100644
--- a/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.module.scss
+++ b/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.module.scss
@@ -1,7 +1,6 @@
@import '../../../styles';
$form-pad-top: calc($pad-md - $border);
-$border-xs: 1px;
$error-line-height: 14px;
.form-field-wrapper {
diff --git a/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.tsx b/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.tsx
index f5f163c01..ffc8c238f 100644
--- a/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.tsx
+++ b/src/lib/form/form-input/form-field-wrapper/FormFieldWrapper.tsx
@@ -9,19 +9,20 @@ export const optionalHint: string = '(optional)'
interface FormFieldWrapperProps {
readonly children: ReactNode
- readonly dirtyOrTouched?: boolean
+ readonly dirty?: boolean
readonly disabled: boolean
readonly error?: string
readonly hint?: string
readonly label: string
readonly name: string
+ readonly touched?: boolean
}
const FormFieldWrapper: FC = (props: FormFieldWrapperProps) => {
const [focusStyle, setFocusStyle]: [string | undefined, Dispatch>] = useState()
- const showError: boolean = !!props.error && !!props.dirtyOrTouched
+ const showError: boolean = !!props.error && (!!props.dirty || !!props.touched)
const formFieldClasses: string = classNames(
styles['form-field'],
props.disabled ? styles.disabled : undefined,
diff --git a/src/lib/form/form-input/input-text/InputText.tsx b/src/lib/form/form-input/input-text/InputText.tsx
index 3cad3ccc3..aa19b14f9 100644
--- a/src/lib/form/form-input/input-text/InputText.tsx
+++ b/src/lib/form/form-input/input-text/InputText.tsx
@@ -8,7 +8,7 @@ import styles from './InputText.module.scss'
export const optionalHint: string = '(optional)'
interface InputTextProps {
- readonly dirtyOrTouched?: boolean
+ readonly dirty?: boolean
readonly disabled?: boolean
readonly error?: string
readonly hint?: string
@@ -19,6 +19,7 @@ interface InputTextProps {
readonly placeholder?: string
readonly preventAutocomplete?: boolean
readonly tabIndex: number
+ readonly touched?: boolean
readonly type: 'password' | 'text'
readonly validateOnBlur?: ValidatorFn
readonly value?: string | number
@@ -28,12 +29,13 @@ const InputText: FC = (props: InputTextProps) => {
return (
= (props: InputTextareaProps) => {
return (