Skip to content

Commit

Permalink
fix: small ui enhancements around nav, default button text and enter …
Browse files Browse the repository at this point in the history
…press
  • Loading branch information
pksorensen committed Aug 19, 2024
1 parent b8c9431 commit 180d2b9
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 33 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"peerDependencies": {
"@griffel/react": "1.5.23",
"@opentelemetry/api": "^1.8.0"
"@opentelemetry/api": "^1.8.0"
},
"devDependencies": {
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/components/icons/ArrowDownIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { IconProps } from "./iconProps";
export const ArrowDownIcon: React.FC<IconProps> = ({ className, color }) => {
return (
<div className={className}>
<svg
<svg
width="24"
height="24"
viewBox="0 0 24 24"
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/components/icons/ArrowLeftIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import type { IconProps } from "./iconProps";

export const ArrowLeftIcon: React.FC<IconProps> = ({ className, color }) => {
return (
<div className={className}>
<svg transform="matrix(6.123233995736766e-17,1,-1,6.123233995736766e-17,0,0)"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
style={{ backgroundColor: 'transparent' }}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 9L12 15L18 9"
stroke={color}
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
);
};
26 changes: 26 additions & 0 deletions packages/core/src/components/icons/ArrowRightIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from "react";
import type { IconProps } from "./iconProps";

export const ArrowRightIcon: React.FC<IconProps> = ({ className, color }) => {
return (

<div className={className}>
<svg transform="matrix(1.8369701987210297e-16,1,1,-1.8369701987210297e-16,0,0)"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
style={{ backgroundColor: 'transparent' }}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 9L12 15L18 9"
stroke={color}
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { useQuickForm } from '../../state/QuickFormContext';
import { ArrowUpIcon, ArrowDownIcon } from '../icons';
import { quickformtokens } from '../../style/quickFormTokensDefinition';
import { makeStyles, mergeClasses, shorthands } from "@griffel/react";
import { ArrowLeftIcon } from '../icons/ArrowLeftIcon';
import { ArrowRightIcon } from '../icons/ArrowRightIcon';

type NavigationButtonProps = {
className?: string;
style?: React.CSSProperties;
horizontal?: boolean;
}

const useNavigationStyles = makeStyles({
Expand All @@ -17,9 +20,11 @@ const useNavigationStyles = makeStyles({
cursor: 'pointer',
...shorthands.borderWidth('1px'),
...shorthands.borderColor(quickformtokens.primary),
stroke: quickformtokens.onPrimary,
backgroundColor: quickformtokens.primary,
':hover': {
stroke: quickformtokens.onPrimary,
backgroundColor: quickformtokens.primary
stroke: quickformtokens.primary,
backgroundColor: quickformtokens.onPrimary
},
},
icon: {
Expand All @@ -30,9 +35,10 @@ const useNavigationStyles = makeStyles({
},
},
disabled: {
backgroundColor: quickformtokens.primaryDarker400,
':hover': {
stroke: quickformtokens.primary,
backgroundColor: 'transparent'
stroke: quickformtokens.onPrimary,
backgroundColor: quickformtokens.primary,
},
},
left: {
Expand All @@ -47,7 +53,7 @@ const useNavigationStyles = makeStyles({
},
});

export const NavigationButton: React.FC<NavigationButtonProps> = ({ className, style }) => {
export const NavigationButton: React.FC<NavigationButtonProps> = ({ className, style, horizontal }) => {
const styles = useNavigationStyles();

const { goToNextSlide, goToPrevSlide, state } = useQuickForm();
Expand All @@ -70,7 +76,11 @@ export const NavigationButton: React.FC<NavigationButtonProps> = ({ className, s
disabled={disablePrevBtn}
onClick={goToPrevSlide}
>
<ArrowDownIcon className={mergeClasses(styles.icon, disablePrevBtn && styles.disabled)} />
{horizontal ?
<ArrowLeftIcon />
:
<ArrowDownIcon />
}
</button>
</label>

Expand All @@ -81,7 +91,11 @@ export const NavigationButton: React.FC<NavigationButtonProps> = ({ className, s
disabled={disableNextBtn}
onClick={goToNextSlide}
>
<ArrowUpIcon className={mergeClasses(styles.icon, disableNextBtn && styles.disabled)} />
{horizontal ?
<ArrowRightIcon />
:
<ArrowUpIcon />
}
</button>
</label>
</div >
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,22 @@ export const BaseInputComponent: React.FC<BaseInputComponentProps> = ({ question
ref.current?.focus();
}, [questionModel.isActive]);

/**
* While a base input component is active we should answer the question upon enter.
*/
useHandleEnterKeypress(!questionModel.isActive, () => {
///**
// * While a base input component is active we should answer the question upon enter.
// */
//useHandleEnterKeypress(!questionModel.isActive, () => {
// answerQuestion(questionModel.logicalName, text, false);
//});

const handleBlur = () => {

if (span) {
span.addEvent("BaseInputComponent:handleBlur");
}
answerQuestion(questionModel.logicalName, text, false);
});
// Add any additional logic you want to execute on blur
};


return (
<div className={mergeClasses(styles.inputContainer, className)} style={style}>
Expand All @@ -152,6 +162,7 @@ export const BaseInputComponent: React.FC<BaseInputComponentProps> = ({ question
placeholder={questionModel.placeholder}
value={text}
onChange={handleChange}
onBlur={handleBlur}
/>
{afterIcon &&
<IconResolver
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/model/SlideModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IconType } from "../components/icons/IconResolver";
import { resolveQuickFormService } from "../services/QuickFormServices";
import { QuestionModel } from "./QuestionModel";
import { QuestionJsonModel } from "./json-definitions/JsonDataModels";
import { QuestionRef } from "./json-definitions/Layout";
import { LayoutDefinition, QuestionRef, SlideLayout } from "./json-definitions/Layout";

export class SlideModel {
displayName?: string;
Expand All @@ -15,6 +15,18 @@ export class SlideModel {
this.rows = rows;
}

static factory(layout?: LayoutDefinition,slide?: SlideLayout ) {
const slideModel= new SlideModel();

slideModel.displayName = slide?.title;
slideModel.buttonText = slide?.buttonText ?? layout?.defaultNextButtonText;
slideModel.icon = slide?.icon ?? layout?.defaultSlideButtonIcon;

return slideModel;

}


addQuestion(layout: QuestionRef, question: QuestionJsonModel, payload: any) {
const mapJsonQuestionToModelQuestion = resolveQuickFormService("questionTransformer");
const questionModel = mapJsonQuestionToModelQuestion(layout.ref, question, payload[question?.logicalName ?? layout.ref])
Expand Down
22 changes: 13 additions & 9 deletions packages/core/src/services/defaults/DefaultModelTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,10 @@ function handleLayout(layout: LayoutDefinition, questions: QuickFormQuestionsDef

if (layout.slides) {
Object.values(layout.slides).forEach(slide => {
const slideModel = new SlideModel();
slideModel.displayName = slide.title;
slideModel.buttonText = slide.buttonText ?? layout.defaultNextButtonText;
slideModel.icon = slide.icon ?? layout.defaultSlideButtonIcon;


const slideModel = SlideModel.factory(layout, slide);

if (slide.rows) {
slideModel.rows = processRows(slide.rows, slideModel, questions, payload);
}
Expand All @@ -106,7 +105,7 @@ function handleLayout(layout: LayoutDefinition, questions: QuickFormQuestionsDef
return slides;
}

function defaultLayout(questions: QuickFormQuestionsDefinition, payload: any): SlideModel[] {
function defaultLayout(questions: QuickFormQuestionsDefinition, payload: any, layout?: LayoutDefinition): SlideModel[] {

const logger = resolveQuickFormService("logger");

Expand All @@ -115,7 +114,8 @@ function defaultLayout(questions: QuickFormQuestionsDefinition, payload: any): S
Object.keys(questions).map((key, index) => [key, index] as [string, number])
.sort(([q1, i1], [q2, i2]) => (questions[q1].order ?? i1) - (questions[q2].order ?? i2))
.map(([questionKey]) => {
let slide: SlideModel = createSlide({ [questionKey]: questions[questionKey] }, payload);
let slide: SlideModel = createSlide({ [questionKey]: questions[questionKey] }, payload,layout);

slides.push(slide);
});

Expand All @@ -124,11 +124,13 @@ function defaultLayout(questions: QuickFormQuestionsDefinition, payload: any): S
return slides;
}

function createSlide(questions: QuickFormQuestionsDefinition, payload: any): SlideModel {
function createSlide(questions: QuickFormQuestionsDefinition, payload: any, layout?: LayoutDefinition): SlideModel {
const logger = resolveQuickFormService("logger");

logger.log("Creating Slides for {@questions}", questions);
const slide = new SlideModel();
// const slide = new SlideModel();
const slide = SlideModel.factory(layout);

// Create rows from questions, assuming each question corresponds to a separate row
const rows: Row[] = Object.entries(questions).map(([questionKey, question]) => {
const questionRef = {
Expand Down Expand Up @@ -195,12 +197,14 @@ const transformJSONInput: QuickFormModelTransformer = (definition, payload): Qui

// Transform questions into slides with rows and columns
if (isDefined(definition.questions)) {


if (definition.layout && definition.layout.slides && Object.keys(definition.layout.slides).length > 0) {
// If layout is defined, assign slides as per layout
slides = handleLayout(definition.layout!, definition.questions, payload);
} else {
// If layout is not defined, assign one question to each slide with only 1 column pr. slide
slides = defaultLayout(definition.questions, payload);
slides = defaultLayout(definition.questions, payload, definition.layout);
}
}
else {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/state/QuickformProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const QuickFormProvider: React.FC<QuickFormProviderProps> = (

const goToSlide = (index: number) => { dispatch({ type: 'SET_INDEX', index: index }); };
const goToNextSlide = () => {
dispatch({ type: 'PROCESS_INTERMEDIATE_QUESTIONS', dispatch });
dispatch({ type: 'PROCESS_INTERMEDIATE_QUESTIONS', dispatch }); //Issue, this starts validation, nextslide should wait on validation if processing.
dispatch({ type: 'NEXT_SLIDE' });
};
const goToPrevSlide = () => { dispatch({ type: 'PREV_SLIDE' }); };
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/state/QuickformState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const defaultState = (data: QuickFormModel = defaultData, layout?: Layout
const defState = {
autoAdvanceSlides: layout?.autoAdvanceSlides ?? false,
enableQuestionNumbers: layout?.enableQuestionNumbers ?? false,
showPressEnter: layout?.showPressEnter ?? undefined,
showPressEnter: layout?.showPressEnter ?? undefined,
defaultNextButtonText: layout?.defaultNextButtonText ?? "Næste",
defaultSlideButtonIcon: layout?.defaultSlideButtonIcon ?? undefined,
classes: layout?.classes ?? {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class NavigationActionHandler {

// Check all visible questions for answered and validity
const validationResult = this.validateQuestions(visibleQuestions);
console.log("reducer validationResult", validationResult)
if (validationResult.isValid) {
return this.computeProgress(NavigationActionHandler.handleSlideChange({ ...state, errorMsg: "" }, 'next'));
} else {
Expand All @@ -62,13 +63,19 @@ export class NavigationActionHandler {
};

static validateQuestions = (questions: QuestionModel[]) => {
const logger = resolveQuickFormService("logger");
if (questions.some((q: { answered: boolean; }) => q.answered === false)) {
return { isValid: false, errorMsg: "Not all questions have been answered." };
}
if (questions.some((q: { output: any; }) => q.output === '' || typeof q.output === "undefined")) {
return { isValid: false, errorMsg: "Some questions are missing outputs." };
}
if (questions.some(q => !q.validationResult || q.validationResult?.isValid === false)) {
if (questions.some(q => !q.validationResult || (!q.validationResult?.isValidating && q.validationResult?.isValid === false))) {
//TODO, if its validating, should that block us from moving to next?
//For now i decided to allow it to move to next - before submitting we can always go back and error hints can be shown in ui.
//I dont think its critical that it allow to move on. Right now this function is only called when validating if it can go to next slide
//it has nothing to do with actually validation of the form.
logger.log("Some questions have failed validation. {questions}", [questions, JSON.stringify(questions)]);
return { isValid: false, errorMsg: "Some questions have failed validation." };
}

Expand Down
15 changes: 11 additions & 4 deletions packages/core/src/style/quickFormTokensDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* Color-scheme is inspired by Material Design (https://m2.material.io/design/color/the-color-system.html#color-theme-creation) */

import { log } from "console";
import { resolveQuickFormService } from "../services/QuickFormServices";
import { camelToKebabCase, defineVariables } from "../utils/quickformUtils";
import { defaultQuickFormTokens } from "./defaultQuickFormTokens";

Expand Down Expand Up @@ -81,11 +83,16 @@ export type QuickFormTokens = QuickFormTokensBase & {
* @returns A flat object with CSS camel-case variable names as keys and their corresponding values.
*/
export const defineQuickFormTokens = (...tokens: Array<Partial<QuickFormTokens>>) => {
const logger = resolveQuickFormService("logger");
logger.log("Merging Quick Form tokens.", tokens);
// Merges and overrides default tokens with provided ones in reverse order for precedence.
const mergedTokens = tokens.reduce((newTokens, currentToken) => ({
...newTokens,
...currentToken,
}), defaultQuickFormTokens);
const mergedTokens = tokens.reduceRight((prevTokens, currentTokens) => {
logger.log("Merging currentTokens into prevTokens", currentTokens, prevTokens);
return ({
...prevTokens,
...currentTokens,
})
}, defaultQuickFormTokens);

// Ensures merged tokens are camelCase CSS variables that React.CSSProperties can use and return.
return defineVariables(mergedTokens);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const useDropDownSelectOptionStyles = makeStyles({

cursor: 'pointer',
...shorthands.transition('background-color', '0.3s'),
overflowX: 'auto',
// overflowX: 'auto',

color: quickformtokens.onSurface,
backgroundColor: 'transparent',
Expand Down Expand Up @@ -62,7 +62,7 @@ export function DropdownSelectOption({
>
{children}
{isSelected && (
<Checkmark color={quickformtokens.onSurface} size={24} />)}
<Checkmark style={{display:"flex"}} color={quickformtokens.onSurface} size={24} />)}
</span>
);
}

0 comments on commit 180d2b9

Please sign in to comment.