Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion src-ts/lib/form/form-definition.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FormButton, FormGroup } from '.'
import { FormButton, FormGroup, FormGroupOptions } from '.'

export type FormAction = 'save' | 'submit' | undefined

Expand All @@ -10,6 +10,7 @@ export interface FormButtons {
export interface FormDefinition {
readonly buttons: FormButtons
readonly groups?: Array<FormGroup>
readonly groupsOptions?: FormGroupOptions
readonly shortName?: string
readonly subtitle?: string
readonly successMessage?: string
Expand Down
28 changes: 22 additions & 6 deletions src-ts/lib/form/form-functions/form.functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ export function initializeValues<T>(inputs: Array<FormInputModel>, formValues?:
inputs
.filter(input => !input.dirty && !input.touched)
.forEach(input => {
input.value = !!(formValues as any)?.hasOwnProperty(input.name)
? (formValues as any)[input.name]
: undefined
if (input.type === 'checkbox') {
input.value = input.checked || false
} else {
input.value = !!(formValues as any)?.hasOwnProperty(input.name)
? (formValues as any)[input.name]
: undefined
}
})
}

Expand Down Expand Up @@ -64,6 +68,7 @@ export async function onSubmitAsync<T>(
formValue: T,
save: (value: T) => Promise<void>,
onSuccess?: () => void,
customValidateForm?: (formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray<FormInputModel>) => boolean
): Promise<void> {

event.preventDefault()
Expand All @@ -80,7 +85,7 @@ export async function onSubmitAsync<T>(
// want to have it look like the submit succeeded
const formValues: HTMLFormControlsCollection = (event.target as HTMLFormElement).elements
if (action === 'submit') {
const isValid: boolean = validateForm(formValues, action, inputs)
const isValid: boolean = (customValidateForm || validateForm)(formValues, action, inputs)
if (!isValid) {
return Promise.reject()
}
Expand Down Expand Up @@ -115,13 +120,22 @@ function handleFieldEvent<T>(input: HTMLInputElement | HTMLTextAreaElement, inpu

const inputDef: FormInputModel = getInputModel(inputs, input.name)

const inputEl: HTMLInputElement = input as HTMLInputElement

if (event === 'change') {
inputDef.dirty = input.value !== originalValue
}
inputDef.touched = true

// set the def value
inputDef.value = input.value
if (input.type === 'checkbox') {
inputDef.value = inputEl.checked
inputDef.checked = inputEl.checked
} else if (input.type === 'file') {
inputDef.value = inputEl.files || undefined
} else {
inputDef.value = input.value
}

// now let's validate the field
const formElements: HTMLFormControlsCollection = (input.form as HTMLFormElement).elements
Expand Down Expand Up @@ -171,7 +185,9 @@ function validateField(formInputDef: FormInputModel, formElements: HTMLFormContr
})
}

export function validateForm(formElements: HTMLFormControlsCollection, event: 'blur' | 'change' | 'submit' | 'initial', inputs: ReadonlyArray<FormInputModel>): boolean {
export type ValidationEvent = 'blur' | 'change' | 'submit' | 'initial'

export function validateForm(formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray<FormInputModel>): boolean {
const errors: ReadonlyArray<FormInputModel> = inputs?.filter(formInputDef => {
formInputDef.dirty = formInputDef.dirty || event === 'submit'
validateField(formInputDef, formElements, event)
Expand Down
7 changes: 7 additions & 0 deletions src-ts/lib/form/form-group.model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CSSProperties } from 'react'

import { FormInputModel } from './form-input.model'

export interface FormGroup {
Expand All @@ -6,3 +8,8 @@ export interface FormGroup {
readonly instructions?: string
readonly title?: string
}

export interface FormGroupOptions {
groupWrapStyles?: CSSProperties
renderGroupDividers?: boolean
}
6 changes: 6 additions & 0 deletions src-ts/lib/form/form-groups/FormGroups.module.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
@import "../../styles/includes";

.form-groups {
display: grid;
grid-template-columns: 1fr;
justify-content: center;

@include ltemd {
grid-template-columns: 1fr !important;
}
}
64 changes: 51 additions & 13 deletions src-ts/lib/form/form-groups/FormGroups.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ChangeEvent, FocusEvent } from 'react'

import { PageDivider } from '../../page-divider'
import { FormDefinition } from '../form-definition.model'
import { FormGroup } from '../form-group.model'
import { FormInputModel } from '../form-input.model'

import { FormCardSet } from './form-card-set'
import FormGroupItem from './form-group-item/FormGroupItem'
import { InputRating, InputText, InputTextarea } from './form-input'
import { InputImagePicker, InputRating, InputText, InputTextarea } from './form-input'
import { FormInputRow } from './form-input-row'
import { InputTextTypes } from './form-input/input-text/InputText'
import FormRadio from './form-radio'
Expand All @@ -23,15 +24,18 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr

const { formDef, onBlur, onChange }: FormGroupsProps = props

const getTabIndex: (input: FormInputModel, index: number) => number = (input, index) => {
function getTabIndex(input: FormInputModel, index: number): number {
const tabIndex: number = input.notTabbable ? -1 : index + 1 + (formDef.tabIndexStart || 0)
return tabIndex
}

const renderInputField: (input: FormInputModel, index: number) => JSX.Element | undefined = (input, index) => {
function renderInputField(input: FormInputModel, index: number): JSX.Element | undefined {

const tabIndex: number = getTabIndex(input, index)

let inputElement: JSX.Element

/* tslint:disable:cyclomatic-complexity */
switch (input.type) {

case 'rating':
Expand All @@ -40,23 +44,33 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
{...input}
onChange={onChange}
tabIndex={tabIndex}
value={input.value}
value={input.value as number | undefined}
/>
)
break

case 'textarea':
inputElement = (
<InputTextarea
{...input}
onBlur={onBlur}
onChange={onChange}
tabIndex={tabIndex}
value={input.value}
value={input.value as string | undefined}
/>
)
break
case 'checkbox':
inputElement = (
<InputText
{...input}
checked={!!input.value}
onBlur={onBlur}
onChange={onChange}
tabIndex={tabIndex}
type='checkbox'
/>
)
break
case 'radio':
inputElement = (
<FormRadio
Expand All @@ -75,6 +89,15 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
/>
)
break
case 'image-picker':
inputElement = (
<InputImagePicker
{...input}
onChange={onChange}
value={input.value}
/>
)
break
default:
inputElement = (
<InputText
Expand All @@ -83,7 +106,7 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
onChange={onChange}
tabIndex={tabIndex}
type={input.type as InputTextTypes || 'text'}
value={input.value}
value={input.value as string | undefined}
/>
)
break
Expand All @@ -100,14 +123,29 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
)
}

const formGroups: Array<JSX.Element | undefined> = formDef?.groups?.map((element: FormGroup, index: number) => {
return <FormGroupItem key={`element-${index}`} group={element} renderFormInput={renderInputField} totalGroupCount={formDef.groups?.length || 0} />
}) || []
const formGroups: Array<JSX.Element | undefined> = formDef?.groups
?.map((element: FormGroup, index: number) => {
return (
<FormGroupItem
key={`element-${index}`}
group={element}
renderFormInput={renderInputField}
totalGroupCount={formDef.groups?.length || 0}
renderDividers={props.formDef.groupsOptions?.renderGroupDividers}
/>
)
})
|| []

return (
<div className={styles['form-groups']}>
{formGroups}
</div>
<>
<div className={styles['form-groups']} style={props.formDef.groupsOptions?.groupWrapStyles}>
{formGroups}
</div>
{
props.formDef.groupsOptions?.renderGroupDividers === false && <PageDivider />
}
</>
)
}

Expand Down
12 changes: 8 additions & 4 deletions src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import styles from './FormGroupItem.module.scss'

interface FormGroupItemProps {
group: FormGroup
renderDividers?: boolean
renderFormInput: (input: FormInputModel, index: number) => JSX.Element | undefined
totalGroupCount: number
}
Expand All @@ -19,10 +20,11 @@ interface ItemRowProps {
hasMultipleGroups: boolean,
instructions?: string | undefined,
isMultiFieldGroup: boolean,
renderDividers?: boolean
title?: string,
}

const TwoColumnItem: React.FC<ItemRowProps> = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title }: ItemRowProps) => {
const TwoColumnItem: React.FC<ItemRowProps> = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title, renderDividers }: ItemRowProps) => {
return (
<>
<div className={cn(styles['form-group-item'], !isMultiFieldGroup && styles['single-field'])}>
Expand All @@ -41,7 +43,9 @@ const TwoColumnItem: React.FC<ItemRowProps> = ({ element, formInputs, hasMultipl
{formInputs}
</div>
</div>
<PageDivider styleNames={[!hasMultipleGroups ? 'spacingSmall' : '']} />
{
renderDividers !== false && <PageDivider styleNames={[!hasMultipleGroups ? 'spacingSmall' : '']} />
}
</>
)
}
Expand All @@ -67,7 +71,7 @@ const SingleColumnItem: React.FC<ItemRowProps> = ({ formInputs, hasMultipleGroup
)
}

const FormGroupItem: React.FC<FormGroupItemProps> = ({ group, renderFormInput, totalGroupCount }: FormGroupItemProps) => {
const FormGroupItem: React.FC<FormGroupItemProps> = ({ group, renderDividers, renderFormInput, totalGroupCount }: FormGroupItemProps) => {
const { instructions, title, inputs, element }: FormGroup = group

const formInputs: Array<JSX.Element | undefined> = inputs?.map((field: FormInputModel, index: number) => renderFormInput(field as FormInputModel, index)) || []
Expand All @@ -77,7 +81,7 @@ const FormGroupItem: React.FC<FormGroupItemProps> = ({ group, renderFormInput, t

return isCardSet ?
<SingleColumnItem hasMultipleGroups={hasMultipleGroups} instructions={instructions} isMultiFieldGroup={isMultiFieldGroup} formInputs={formInputs} title={title} /> :
<TwoColumnItem hasMultipleGroups={hasMultipleGroups} element={element} instructions={instructions} isMultiFieldGroup={isMultiFieldGroup} formInputs={formInputs} title={title} />
<TwoColumnItem hasMultipleGroups={hasMultipleGroups} element={element} instructions={instructions} isMultiFieldGroup={isMultiFieldGroup} formInputs={formInputs} title={title} renderDividers={renderDividers} />
}

export default FormGroupItem
1 change: 1 addition & 0 deletions src-ts/lib/form/form-groups/form-input/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './input-image-picker'
export * from './form-input-autcomplete-option.enum'
export * from './input-rating'
export * from './input-select'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@import "../../../../styles/includes";
@import "../../../../styles/variables";

.filePicker {
display: flex;
align-items: center;
justify-content: center;
background-color: $black-5;
border-radius: 8px;
width: 132px;
height: 132px;
position: relative;
margin-bottom: $space-xl;

@include ltemd {
width: 100%;
}

.filePickerPlaceholder {
color: $turq-160;
text-align: center;
font-weight: $font-weight-bold;
}

.filePickerPencil {
position: absolute;
top: 0;
right: 0;
color: $turq-160;
}

.filePickerInput {
display: none;
}

.badgeImage {
width: 72px;
height: 72px;
}
}
Loading