Skip to content

Commit 5c5f502

Browse files
authored
Merge pull request #305 from topcoder-platform/feat/GAME-82
Feat/game 82
2 parents 99844c2 + 1287f14 commit 5c5f502

31 files changed

+574
-63
lines changed

src-ts/lib/form/form-definition.model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FormButton, FormGroup } from '.'
1+
import { FormButton, FormGroup, FormGroupOptions } from '.'
22

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

@@ -10,6 +10,7 @@ export interface FormButtons {
1010
export interface FormDefinition {
1111
readonly buttons: FormButtons
1212
readonly groups?: Array<FormGroup>
13+
readonly groupsOptions?: FormGroupOptions
1314
readonly shortName?: string
1415
readonly subtitle?: string
1516
readonly successMessage?: string

src-ts/lib/form/form-functions/form.functions.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ export function initializeValues<T>(inputs: Array<FormInputModel>, formValues?:
3333
inputs
3434
.filter(input => !input.dirty && !input.touched)
3535
.forEach(input => {
36-
input.value = !!(formValues as any)?.hasOwnProperty(input.name)
37-
? (formValues as any)[input.name]
38-
: undefined
36+
if (input.type === 'checkbox') {
37+
input.value = input.checked || false
38+
} else {
39+
input.value = !!(formValues as any)?.hasOwnProperty(input.name)
40+
? (formValues as any)[input.name]
41+
: undefined
42+
}
3943
})
4044
}
4145

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

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

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

123+
const inputEl: HTMLInputElement = input as HTMLInputElement
124+
118125
if (event === 'change') {
119126
inputDef.dirty = input.value !== originalValue
120127
}
121128
inputDef.touched = true
122129

123130
// set the def value
124-
inputDef.value = input.value
131+
if (input.type === 'checkbox') {
132+
inputDef.value = inputEl.checked
133+
inputDef.checked = inputEl.checked
134+
} else if (input.type === 'file') {
135+
inputDef.value = inputEl.files || undefined
136+
} else {
137+
inputDef.value = input.value
138+
}
125139

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

174-
export function validateForm(formElements: HTMLFormControlsCollection, event: 'blur' | 'change' | 'submit' | 'initial', inputs: ReadonlyArray<FormInputModel>): boolean {
188+
export type ValidationEvent = 'blur' | 'change' | 'submit' | 'initial'
189+
190+
export function validateForm(formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray<FormInputModel>): boolean {
175191
const errors: ReadonlyArray<FormInputModel> = inputs?.filter(formInputDef => {
176192
formInputDef.dirty = formInputDef.dirty || event === 'submit'
177193
validateField(formInputDef, formElements, event)

src-ts/lib/form/form-group.model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CSSProperties } from 'react'
2+
13
import { FormInputModel } from './form-input.model'
24

35
export interface FormGroup {
@@ -6,3 +8,8 @@ export interface FormGroup {
68
readonly instructions?: string
79
readonly title?: string
810
}
11+
12+
export interface FormGroupOptions {
13+
groupWrapStyles?: CSSProperties
14+
renderGroupDividers?: boolean
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
@import "../../styles/includes";
2+
13
.form-groups {
24
display: grid;
35
grid-template-columns: 1fr;
46
justify-content: center;
7+
8+
@include ltemd {
9+
grid-template-columns: 1fr !important;
10+
}
511
}

src-ts/lib/form/form-groups/FormGroups.tsx

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { ChangeEvent, FocusEvent } from 'react'
22

3+
import { PageDivider } from '../../page-divider'
34
import { FormDefinition } from '../form-definition.model'
45
import { FormGroup } from '../form-group.model'
56
import { FormInputModel } from '../form-input.model'
67

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

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

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

31-
const renderInputField: (input: FormInputModel, index: number) => JSX.Element | undefined = (input, index) => {
32+
function renderInputField(input: FormInputModel, index: number): JSX.Element | undefined {
33+
3234
const tabIndex: number = getTabIndex(input, index)
3335

3436
let inputElement: JSX.Element
37+
38+
/* tslint:disable:cyclomatic-complexity */
3539
switch (input.type) {
3640

3741
case 'rating':
@@ -40,23 +44,33 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
4044
{...input}
4145
onChange={onChange}
4246
tabIndex={tabIndex}
43-
value={input.value}
47+
value={input.value as number | undefined}
4448
/>
4549
)
4650
break
47-
4851
case 'textarea':
4952
inputElement = (
5053
<InputTextarea
5154
{...input}
5255
onBlur={onBlur}
5356
onChange={onChange}
5457
tabIndex={tabIndex}
55-
value={input.value}
58+
value={input.value as string | undefined}
5659
/>
5760
)
5861
break
5962
case 'checkbox':
63+
inputElement = (
64+
<InputText
65+
{...input}
66+
checked={!!input.value}
67+
onBlur={onBlur}
68+
onChange={onChange}
69+
tabIndex={tabIndex}
70+
type='checkbox'
71+
/>
72+
)
73+
break
6074
case 'radio':
6175
inputElement = (
6276
<FormRadio
@@ -75,6 +89,15 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
7589
/>
7690
)
7791
break
92+
case 'image-picker':
93+
inputElement = (
94+
<InputImagePicker
95+
{...input}
96+
onChange={onChange}
97+
value={input.value}
98+
/>
99+
)
100+
break
78101
default:
79102
inputElement = (
80103
<InputText
@@ -83,7 +106,7 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
83106
onChange={onChange}
84107
tabIndex={tabIndex}
85108
type={input.type as InputTextTypes || 'text'}
86-
value={input.value}
109+
value={input.value as string | undefined}
87110
/>
88111
)
89112
break
@@ -100,14 +123,29 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr
100123
)
101124
}
102125

103-
const formGroups: Array<JSX.Element | undefined> = formDef?.groups?.map((element: FormGroup, index: number) => {
104-
return <FormGroupItem key={`element-${index}`} group={element} renderFormInput={renderInputField} totalGroupCount={formDef.groups?.length || 0} />
105-
}) || []
126+
const formGroups: Array<JSX.Element | undefined> = formDef?.groups
127+
?.map((element: FormGroup, index: number) => {
128+
return (
129+
<FormGroupItem
130+
key={`element-${index}`}
131+
group={element}
132+
renderFormInput={renderInputField}
133+
totalGroupCount={formDef.groups?.length || 0}
134+
renderDividers={props.formDef.groupsOptions?.renderGroupDividers}
135+
/>
136+
)
137+
})
138+
|| []
106139

107140
return (
108-
<div className={styles['form-groups']}>
109-
{formGroups}
110-
</div>
141+
<>
142+
<div className={styles['form-groups']} style={props.formDef.groupsOptions?.groupWrapStyles}>
143+
{formGroups}
144+
</div>
145+
{
146+
props.formDef.groupsOptions?.renderGroupDividers === false && <PageDivider />
147+
}
148+
</>
111149
)
112150
}
113151

src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import styles from './FormGroupItem.module.scss'
99

1010
interface FormGroupItemProps {
1111
group: FormGroup
12+
renderDividers?: boolean
1213
renderFormInput: (input: FormInputModel, index: number) => JSX.Element | undefined
1314
totalGroupCount: number
1415
}
@@ -19,10 +20,11 @@ interface ItemRowProps {
1920
hasMultipleGroups: boolean,
2021
instructions?: string | undefined,
2122
isMultiFieldGroup: boolean,
23+
renderDividers?: boolean
2224
title?: string,
2325
}
2426

25-
const TwoColumnItem: React.FC<ItemRowProps> = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title }: ItemRowProps) => {
27+
const TwoColumnItem: React.FC<ItemRowProps> = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title, renderDividers }: ItemRowProps) => {
2628
return (
2729
<>
2830
<div className={cn(styles['form-group-item'], !isMultiFieldGroup && styles['single-field'])}>
@@ -41,7 +43,9 @@ const TwoColumnItem: React.FC<ItemRowProps> = ({ element, formInputs, hasMultipl
4143
{formInputs}
4244
</div>
4345
</div>
44-
<PageDivider styleNames={[!hasMultipleGroups ? 'spacingSmall' : '']} />
46+
{
47+
renderDividers !== false && <PageDivider styleNames={[!hasMultipleGroups ? 'spacingSmall' : '']} />
48+
}
4549
</>
4650
)
4751
}
@@ -67,7 +71,7 @@ const SingleColumnItem: React.FC<ItemRowProps> = ({ formInputs, hasMultipleGroup
6771
)
6872
}
6973

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

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

7882
return isCardSet ?
7983
<SingleColumnItem hasMultipleGroups={hasMultipleGroups} instructions={instructions} isMultiFieldGroup={isMultiFieldGroup} formInputs={formInputs} title={title} /> :
80-
<TwoColumnItem hasMultipleGroups={hasMultipleGroups} element={element} instructions={instructions} isMultiFieldGroup={isMultiFieldGroup} formInputs={formInputs} title={title} />
84+
<TwoColumnItem hasMultipleGroups={hasMultipleGroups} element={element} instructions={instructions} isMultiFieldGroup={isMultiFieldGroup} formInputs={formInputs} title={title} renderDividers={renderDividers} />
8185
}
8286

8387
export default FormGroupItem

src-ts/lib/form/form-groups/form-input/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './input-image-picker'
12
export * from './form-input-autcomplete-option.enum'
23
export * from './input-rating'
34
export * from './input-select'
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@import "../../../../styles/includes";
2+
@import "../../../../styles/variables";
3+
4+
.filePicker {
5+
display: flex;
6+
align-items: center;
7+
justify-content: center;
8+
background-color: $black-5;
9+
border-radius: 8px;
10+
width: 132px;
11+
height: 132px;
12+
position: relative;
13+
margin-bottom: $space-xl;
14+
15+
@include ltemd {
16+
width: 100%;
17+
}
18+
19+
.filePickerPlaceholder {
20+
color: $turq-160;
21+
text-align: center;
22+
font-weight: $font-weight-bold;
23+
}
24+
25+
.filePickerPencil {
26+
position: absolute;
27+
top: 0;
28+
right: 0;
29+
color: $turq-160;
30+
}
31+
32+
.filePickerInput {
33+
display: none;
34+
}
35+
36+
.badgeImage {
37+
width: 72px;
38+
height: 72px;
39+
}
40+
}

0 commit comments

Comments
 (0)