Skip to content

Commit 745ed72

Browse files
authored
Merge pull request #14 from EAVFW/origin/kba/input-properties
Origin/kba/input properties
2 parents f99eb99 + 4c8448d commit 745ed72

File tree

17 files changed

+136
-162
lines changed

17 files changed

+136
-162
lines changed
Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,73 @@
1-
"use client";
21
import React, { useState, useEffect } from 'react';
3-
import { useDelayedClickListener } from "../../hooks";
42
import { useQuickForm } from "../../state/QuickFormContext";
53
import { quickformtokens } from '../../style/quickFormTokensDefinition';
64

75
type ErrorPopupProps = {
86
readonly message: string;
97
};
108

11-
export const ErrorPopup: React.FC<ErrorPopupProps> = ({ message }: ErrorPopupProps) => {
9+
export const ErrorPopup: React.FC<ErrorPopupProps> = ({ message }) => {
1210
const [isVisible, setIsVisible] = useState(false);
13-
const { dispatch, state } = useQuickForm();
11+
const [opacity, setOpacity] = useState(0);
12+
const { dispatch } = useQuickForm();
1413

15-
/**
16-
* DISCUSS - What cases is there for resetting error and can it be handled in reducer all alone?.
17-
* When an error is shown, upon next answer it can be cleared.
18-
* Possible a dissmis button - but i dont think it should automatically just remove when clicked.
19-
*/
20-
21-
22-
//const resetErrorMessage = () => {
23-
// if (state.errorMsg !== "") {
24-
// dispatch({ type: "SET_ERROR_MSG", msg: "" })
25-
// }
26-
//}
27-
28-
// useDelayedClickListener(resetErrorMessage);
14+
const resetErrorPopup = () => {
15+
dispatch({ type: 'SET_ERROR_MSG', msg: "" });
16+
setIsVisible(false);
17+
setOpacity(0);
18+
}
2919

3020
useEffect(() => {
21+
let timer: NodeJS.Timeout;
3122
if (message) {
3223
setIsVisible(true);
33-
setTimeout(() => setIsVisible(false), 350);
24+
setOpacity(0);
25+
setTimeout(() => setOpacity(1), 10);
26+
27+
timer = setTimeout(() => {
28+
resetErrorPopup();
29+
}, 3000);
30+
} else {
31+
resetErrorPopup();
3432
}
33+
34+
return () => {
35+
clearTimeout(timer);
36+
setOpacity(0);
37+
};
3538
}, [message]);
3639

37-
if (message === "") {
38-
return <></>;
39-
}
40+
if (!isVisible) return null;
4041

41-
const errorStyle: React.CSSProperties = {
42-
alignItems: 'flex-end',
43-
animation: isVisible ? 'slide-up 0.35s linear 1 forwards' : '',
44-
backgroundColor: quickformtokens.error,
45-
borderRadius: '3px',
46-
color: quickformtokens.onError,
47-
display: 'flex',
48-
fontSize: '1.5rem',
49-
marginTop: '15px',
50-
padding: '8px 12px',
51-
width: 'max-content',
52-
};
53-
54-
const mobileErrorStyle: React.CSSProperties = {
55-
...errorStyle,
56-
fontSize: '1.75rem',
57-
marginTop: '22px',
42+
const backdropStyle: React.CSSProperties = {
43+
position: 'fixed',
44+
top: 0,
45+
left: 0,
5846
width: '100%',
47+
height: '100%',
48+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
49+
display: 'flex',
50+
justifyContent: 'center',
51+
alignItems: 'center',
52+
zIndex: 1000,
5953
};
6054

61-
const imgStyle: React.CSSProperties = {
62-
marginRight: '4px',
55+
const toastStyle: React.CSSProperties = {
56+
padding: '20px',
57+
borderRadius: '10px',
58+
backgroundColor: quickformtokens.error,
59+
color: quickformtokens.onError,
60+
fontSize: '16px',
61+
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
62+
opacity: opacity,
63+
transition: 'opacity 0.5s ease-in',
6364
};
6465

6566
return (
66-
<div style={window.innerWidth <= 599 ? mobileErrorStyle : errorStyle}>
67-
{/* If there's an image you want to include inside the error message */}
68-
{/* <img src="path_to_your_image" alt="Error" style={imgStyle} /> */}
69-
{message}
67+
<div style={backdropStyle} onClick={resetErrorPopup}>
68+
<div style={toastStyle} onClick={(e) => e.stopPropagation()}>
69+
{message}
70+
</div>
7071
</div>
7172
);
7273
};

packages/core/src/components/question/Question.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const Question: React.FC<QuestionProps> = ({ model, style }) => {
6565
questionModel={model}
6666
{...model.inputProperties ?? {}}
6767
/>
68-
{model.validationResult?.message !== "" && <ErrorMessage message={model.validationResult?.message} />}
68+
{typeof (model.validationResult?.message) !== "undefined" && model.validationResult?.message !== "" && <ErrorMessage message={model.validationResult?.message} />}
6969
</div>
7070
);
7171
}

packages/core/src/hooks/useFocusableQuestion.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useRef } from "react";
1+
import { useEffect, useRef } from "react";
22
import { useQuickForm } from "../state";
33

44

@@ -10,8 +10,8 @@ export const useFocusableQuestion = <T extends HTMLElement>(questionkey: string,
1010
if (ref.current && isFirstQuestionInCurrentSlide(questionkey)) {
1111
ref.current.focus(options);
1212
}
13-
}, [ref,questionkey]);
14-
13+
}, [ref, questionkey]);
14+
1515

1616
return ref;
1717
}

packages/core/src/model/InputType.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,23 @@ export type InputProps<TProps = InputPropertiesTypes> = {
99

1010
const Email = "email";
1111
const Multilinetext = "multilinetext";
12-
const Radio = "radio";
13-
const Slider = "slider";
1412
const Text = "text";
15-
const Buttons = "buttons";
1613
const Phone = "phone";
1714

1815
export interface InputTypeMap {
19-
[Buttons]: ButtonsProperties;
2016
[Phone]: PhoneProperties;
2117
[Email]: EmailProperties;
2218
[Multilinetext]: MultilineProperties;
23-
[Radio]: RadioProperties;
24-
[Slider]: SliderProperties;
2519
[Text]: TextProperties;
2620
}
2721

2822
export type InputPropertiesTypes =
29-
ButtonsProperties |
3023
EmailProperties |
3124
MultilineProperties |
32-
RadioProperties |
33-
SliderProperties |
3425
TextProperties |
3526
PhoneProperties |
3627
{};
3728

38-
export type ButtonsProperties = {
39-
inputType: typeof Buttons;
40-
options: {
41-
key: string | undefined;
42-
label: string;
43-
}
44-
defaultValue?: string;
45-
}
46-
47-
4829
export type PhoneProperties = {
4930
inputType: typeof Phone;
5031
defaultValue?: number;
@@ -60,23 +41,6 @@ export type MultilineProperties = {
6041
defaultValue?: string;
6142
}
6243

63-
export type RadioProperties = {
64-
inputType: typeof Radio;
65-
options: {
66-
[key: string]: string;
67-
}
68-
defaultValue?: boolean;
69-
direction?: "horizontal" | "vertical";
70-
}
71-
72-
export type SliderProperties = {
73-
inputType: typeof Slider;
74-
min: number;
75-
max: number;
76-
step: number;
77-
defaultValue?: number;
78-
}
79-
8044
export type TextProperties = {
8145
inputType: typeof Text;
8246
defaultValue?: string;

packages/core/src/model/QuickFormModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EndingModel, IntroModel, QuestionModel, SlideModel, SubmitModel } from "./index";
1+
import { EndingModel, IntroModel, SlideModel, SubmitModel } from "./index";
22

33
export type QuickFormModel = {
44
intro?: IntroModel;

packages/core/src/model/json-definitions/JsonDataModels.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ButtonsProperties, EmailProperties, MultilineProperties, RadioProperties, SliderProperties, TextProperties } from "../InputType";
1+
import { EmailProperties, MultilineProperties, TextProperties } from "../InputType";
22

33
type QuickFormQuestionDefinition = {
44

@@ -49,7 +49,7 @@ type QuickFormQuestionDefinition = {
4949

5050
/**
5151
* All questions support conditional rendering, allowing one to specify a rule and a engine to execute it.
52-
* TODO: the rule should be of type any, because its the engine (type) that knows its data type.
52+
* The rule is of type any, because its the engine (type) that knows its data type.
5353
*/
5454
visible?: {
5555
engine: string;
@@ -68,12 +68,7 @@ type QuickFormQuestionDefinition = {
6868
*
6969
* TODO - need to be able to extend this in a meaning full way.
7070
*/
71-
export type QuestionJsonModel =
72-
QuickFormQuestionDefinition |
73-
QuickFormQuestionDefinition & ButtonsProperties |
74-
// QuickFormQuestionDefinition & DropDownProperties |
75-
QuickFormQuestionDefinition & EmailProperties |
76-
QuickFormQuestionDefinition & MultilineProperties |
77-
QuickFormQuestionDefinition & RadioProperties |
78-
QuickFormQuestionDefinition & SliderProperties |
79-
QuickFormQuestionDefinition & TextProperties;
71+
export type QuestionJsonModel = QuickFormQuestionDefinition
72+
| QuickFormQuestionDefinition & EmailProperties
73+
| QuickFormQuestionDefinition & MultilineProperties
74+
| QuickFormQuestionDefinition & TextProperties;

packages/core/src/services/QuickFormServices.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { InputPropertiesTypes, QuestionModel, QuickFormModel } from "../model";
44
import { QuickFormDefinition } from "../model";
55
import { QuestionJsonModel } from "../model/json-definitions/JsonDataModels";
66
import { InputComponentType } from "./defaults/DefaultInputTypeResolver";
7+
import { QuickformState } from "../state";
78

89
export type HeadingNumberDisplayProvider = () => boolean;
910
export type QuickFormModelTransformer = (data: QuickFormDefinition, payload: any) => QuickFormModel;
1011
export type QuestionTransformer = (key: string, question: QuestionJsonModel, value?: any, visible?: { type: string; rule: string; }) => QuestionModel;
1112
export type InputTypePropertiesTransformer = (questionJsonModel: QuestionJsonModel) => InputPropertiesTypes | undefined;
1213
export type RegisterInputTypeComponent = (key: string, component: InputComponentType) => void;
13-
export type InputValidator = <TProps extends InputPropertiesTypes>(questionModel: QuestionModel<TProps>) => Promise<ValidationResult>;
14+
export type InputValidator = <TProps extends InputPropertiesTypes>(questionModel: QuestionModel<TProps>, state: QuickformState) => Promise<ValidationResult>;
1415
export interface IQuickFormLogger {
1516
log(body: string, ...args: any[]): void;
1617
warn(body: string, ...args: any[]): void;

packages/core/src/services/defaults/DefaultInputTypeResolver.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FC } from "react";
2-
import { RadioProperties, SliderProperties, ButtonsProperties, InputPropertiesTypes, InputProps } from "../../model";
2+
import { InputPropertiesTypes, InputProps } from "../../model";
33
import { QuestionJsonModel } from "../../model/json-definitions/JsonDataModels";
44
import { registerQuickFormService } from "../QuickFormServices";
55

@@ -19,23 +19,7 @@ const parseInputProperties = (questionJsonModel: QuestionJsonModel): InputProper
1919
.map(([key, schema]) => [key, questionJsonModel[key as keyof QuestionJsonModel] ?? getDefaultValue(schema)])) as InputPropertiesTypes;
2020
}
2121

22-
const inputTypePropertiesMap: { [key: string]: () => InputPropertiesTypes } = {
23-
buttons: () => ({
24-
inputType,
25-
options: (questionJsonModel as QuestionJsonModel & ButtonsProperties).options
26-
}),
27-
radio: () => ({
28-
inputType,
29-
options: (questionJsonModel as QuestionJsonModel & RadioProperties).options,
30-
direction: (questionJsonModel as QuestionJsonModel & RadioProperties).direction
31-
}),
32-
slider: () => ({
33-
inputType,
34-
min: (questionJsonModel as QuestionJsonModel & SliderProperties).min,
35-
max: (questionJsonModel as QuestionJsonModel & SliderProperties).max,
36-
step: (questionJsonModel as QuestionJsonModel & SliderProperties).step
37-
}),
38-
};
22+
const inputTypePropertiesMap: { [key: string]: () => InputPropertiesTypes } = {};
3923

4024
return inputType in inputTypePropertiesMap ? inputTypePropertiesMap[inputType]() : {};
4125
};
@@ -85,9 +69,6 @@ const ThrowIfUsed: InputComponentType = (props) => { throw new Error("Not regist
8569
const inputComponents: InputComponentDictionary = {
8670
text: ThrowIfUsed,
8771
none: ThrowIfUsed,
88-
dropdown: ThrowIfUsed,
89-
slider: ThrowIfUsed,
90-
toggle: ThrowIfUsed,
9172
multilinetext: ThrowIfUsed
9273
};
9374

packages/core/src/services/defaults/DefaultInputValidator.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ValidationResult } from "../../model/ValidationResult";
2-
import { InputPropertiesTypes, QuestionModel, SliderProperties } from "../../model";
2+
import { InputPropertiesTypes, QuestionModel } from "../../model";
33
import { registerQuickFormService } from "../QuickFormServices";
4+
import { QuickformState } from "../../state";
45

56
const validateText = (output: any): ValidationResult => {
67
const text = typeof output === 'string' ? output.trim() : '';
@@ -36,8 +37,8 @@ const validateEmail = (output: any): ValidationResult => {
3637
};
3738

3839
const validatePhone = async (output: any): Promise<ValidationResult> => {
39-
// Wait for 4 seconds
40-
await new Promise(resolve => setTimeout(resolve, 4000));
40+
// Wait for 2 seconds to demo
41+
await new Promise(resolve => setTimeout(resolve, 2000));
4142

4243
const phoneRegex = /^[0-9]{8,}$/;
4344
const valid = typeof output === 'string' && phoneRegex.test(output);
@@ -49,29 +50,18 @@ const validatePhone = async (output: any): Promise<ValidationResult> => {
4950
};
5051
};
5152

52-
53-
const validateSlider = (output: any, properties: SliderProperties): ValidationResult => {
54-
const valid = typeof output === 'number' && output >= properties.min && output <= properties.max;
55-
return {
56-
isValid: valid,
57-
message: valid ? "" : `Value must be a number between ${properties.min} and ${properties.max}.`,
58-
validatedOutput: output,
59-
};
60-
};
61-
6253
type ValidatorMap = {
63-
[inputType: string]: (output: any, properties?: any) => Promise<ValidationResult>;
54+
[inputType: string]: ValidatorFunction<any, any, QuestionModel<any>, QuickformState>;
6455
};
6556

6657
const validatorMap: ValidatorMap = {
6758
email: (output: any) => Promise.resolve(validateEmail(output)),
6859
phone: (output: any) => Promise.resolve(validatePhone(output)),
69-
slider: (output: any, properties: SliderProperties) => Promise.resolve(validateSlider(output, properties)),
7060
text: (output: any) => Promise.resolve(validateText(output)),
7161
multilinetext: (output: any) => Promise.resolve(validateMultilineText(output))
7262
};
7363

74-
const validateQuestionOutput = async <TProps extends InputPropertiesTypes>(questionModel: QuestionModel<TProps>): Promise<ValidationResult> => {
64+
const validateQuestionOutput = async <TProps extends InputPropertiesTypes>(questionModel: QuestionModel<TProps>, state: QuickformState): Promise<ValidationResult> => {
7565
const validator = validatorMap[questionModel.inputType];
7666
if (!validator) {
7767
// This is to support if no validation is created for inputtype.. defaults to validated..
@@ -82,14 +72,15 @@ const validateQuestionOutput = async <TProps extends InputPropertiesTypes>(quest
8272
isValidating: false,
8373
timestamp: new Date().getTime()
8474
});
85-
// return Promise.resolve({ isValid: false, message: `No validator available for inputType: ${questionModel.inputType}`, validatedOutput: questionModel.output });
8675
}
8776

88-
return await validator(questionModel.output, questionModel.inputProperties);
77+
return await validator(questionModel.output, questionModel.inputProperties, questionModel,state);
8978
};
9079

91-
export const registerInputTypeValidator = (key: string, validator: (output: any, properties?: any) => Promise<ValidationResult>) => {
92-
validatorMap[key] = validator;
80+
export type ValidatorFunction<TAnswer, TInputProps, TQuestionModel extends QuestionModel<TInputProps>, TQuickFormState extends QuickformState> = (output: TAnswer, properties: TInputProps, questionModel: TQuestionModel, state: TQuickFormState) => Promise<ValidationResult>;
81+
82+
export const registerInputTypeValidator = <TAnswer, TInputProps, TQuestionModel extends QuestionModel<TInputProps>, TQuickFormState extends QuickformState>(key: string, validator: ValidatorFunction<TAnswer, TInputProps, TQuestionModel, TQuickFormState>) => {
83+
validatorMap[key] = validator as ValidatorFunction<any, any, QuestionModel<any>, QuickformState>;
9384
};
9485

9586
registerQuickFormService("inputValidator", validateQuestionOutput);

0 commit comments

Comments
 (0)