Questionnaire progress, preview questions, select answer

This commit is contained in:
Ibitoye Rotimi Best
2021-07-04 22:06:28 +03:00
parent 3b575adc54
commit 48c8d406e2
11 changed files with 292 additions and 84 deletions
@@ -5,9 +5,13 @@
handleRemoveOption, handleRemoveOption,
handleRemoveQuestion, handleRemoveQuestion,
handleCode, handleCode,
handleAnswerSelect,
} from "./store/index"; } from "./store/index";
import Delete24 from "carbon-icons-svelte/lib/Delete24"; import Delete24 from "carbon-icons-svelte/lib/Delete24";
import AddFilled24 from "carbon-icons-svelte/lib/AddFilled24"; import AddFilled24 from "carbon-icons-svelte/lib/AddFilled24";
import CheckmarkFilled24 from "carbon-icons-svelte/lib/CheckmarkFilled24";
import CheckmarkOutline24 from "carbon-icons-svelte/lib/CheckmarkOutline24";
// import Subtract24 from "carbon-icons-svelte/lib/Subtract24";
import TextField from "../../../../Form/TextField.svelte"; import TextField from "../../../../Form/TextField.svelte";
import TextArea from "../../../../Form/TextArea.svelte"; import TextArea from "../../../../Form/TextArea.svelte";
@@ -104,45 +108,71 @@
{/if} {/if}
<div class="flex flex-col mt-2"> <div class="flex flex-col mt-2">
{#each question.options as option} {#if QUESTION_TYPE.RADIO === question.type}
{#if QUESTION_TYPE.RADIO === question.type} {#each question.options as option}
<RadioItem <RadioItem
isEditable={true} isEditable={true}
name={question.title || "radio-name"} name={question.title || "radio-name"}
bind:label={option.value} bind:label={option.value}
> >
<div slot="iconbutton"> <div slot="iconbutton" class="flex items-center">
<IconButton <IconButton
value={option.id} value={option.id}
onClick={handleRemoveOption(question.id, option.id)} onClick={handleRemoveOption(question.id, option.id)}
> >
<Delete24 class="carbon-icon" /> <Delete24 class="carbon-icon" />
</IconButton> </IconButton>
<IconButton
value={option.id}
onClick={handleAnswerSelect(question.id, option.id)}
buttonClassName={question.answers.includes(option.id) &&
"success"}
>
{#if question.answers.includes(option.id)}
<CheckmarkFilled24 class="carbon-icon" />
{:else}
<CheckmarkOutline24 class="carbon-icon" />
{/if}
</IconButton>
</div> </div>
</RadioItem> </RadioItem>
{/if} {/each}
{/if}
{#if QUESTION_TYPE.CHECKBOX === question.type} {#if QUESTION_TYPE.CHECKBOX === question.type}
{#each question.options as option}
<Checkbox <Checkbox
isEditable={true} isEditable={true}
name={question || "checkbox-name"} name={question || "checkbox-name"}
bind:label={option.value} bind:label={option.value}
> >
<div slot="iconbutton"> <div slot="iconbutton" class="flex items-center">
<IconButton <IconButton
value={option.id} value={option.id}
onClick={handleRemoveOption(question.id, option.id)} onClick={handleRemoveOption(question.id, option.id)}
> >
<Delete24 class="carbon-icon" /> <Delete24 class="carbon-icon" />
</IconButton> </IconButton>
<IconButton
value={option.id}
onClick={handleAnswerSelect(question.id, option.id)}
buttonClassName={question.answers.includes(option.id) &&
"success"}
>
{#if question.answers.includes(option.id)}
<CheckmarkFilled24 class="carbon-icon" />
{:else}
<CheckmarkOutline24 class="carbon-icon" />
{/if}
</IconButton>
</div> </div>
</Checkbox> </Checkbox>
{/if} {/each}
{/if}
{#if QUESTION_TYPE.TEXTAREA === question.type} {#if QUESTION_TYPE.TEXTAREA === question.type}
<TextArea bind:value={option.value} /> <TextArea bind:value={question.value} />
{/if} {/if}
{/each}
</div> </div>
{#if QUESTION_TYPE.TEXTAREA !== question.type} {#if QUESTION_TYPE.TEXTAREA !== question.type}
@@ -2,16 +2,19 @@
import { questionnaire } from "./store/index"; import { questionnaire } from "./store/index";
import RadioQuestion from "../../../../Question/RadioQuestion/index.svelte"; import RadioQuestion from "../../../../Question/RadioQuestion/index.svelte";
import CheckboxQuestion from "../../../../Question/CheckboxQuestion/index.svelte"; import CheckboxQuestion from "../../../../Question/CheckboxQuestion/index.svelte";
import TextareaQuestion from "../../../../Question/TextareaQuestion/index.svelte";
import PrimaryButton from "../../../../PrimaryButton/index.svelte"; import PrimaryButton from "../../../../PrimaryButton/index.svelte";
import Progress from "../../../../Progress/index.svelte";
import { QUESTION_TYPE } from "../../../../Question/constants";
export let preview;
import {
QUESTION_TYPE,
QUESTION_TYPES,
} from "../../../../Question/constants";
import Checkbox from "../../../../Form/Checkbox.svelte";
let answers = {}; let answers = {};
let currentQuestionIndex = 0; let currentQuestionIndex = 0;
let currentQuestion = {}; let currentQuestion = {};
let renderProps = {};
let progressValue = 0;
const { questions, title, description } = $questionnaire; const { questions, title, description } = $questionnaire;
@@ -20,7 +23,7 @@
} }
function onSubmit(name, value) { function onSubmit(name, value) {
if (!value) return; // if (!value) return;
answers = { answers = {
...answers, ...answers,
[name]: value, [name]: value,
@@ -28,28 +31,81 @@
currentQuestionIndex += 1; currentQuestionIndex += 1;
} }
function onCheckboxSubmit(name, value) {
if (!value) return;
answers = {
...answers,
[name]: value,
};
currentQuestionIndex += 1;
}
$: {
currentQuestion = questions[currentQuestionIndex - 1];
console.log("currentQuestion", currentQuestion);
}
function onPrevious() { function onPrevious() {
currentQuestionIndex -= 1; currentQuestionIndex -= 1;
} }
function getRenderProps(question, questionIndex) {
return {
title: questionIndex + ". " + question.title,
name: `${question.id}`,
options: question.options,
code: question.code,
defaultValue:
question.type === QUESTION_TYPE.TEXTAREA
? question.value || ""
: answers[question.id] || [],
onSubmit,
onPrevious,
disablePreviousButton: questionIndex === 1,
isLast: questionIndex === questions.length,
isPreview: preview,
};
}
function getProgressValue(currentQuestionIndex) {
if (!currentQuestion) {
return 100;
}
return (
Math.round(((currentQuestionIndex - 1) / questions.length) * 100) || 0
);
}
$: {
currentQuestion = questions[currentQuestionIndex - 1];
if (currentQuestion) {
renderProps = getRenderProps(currentQuestion, currentQuestionIndex);
}
}
$: progressValue = getProgressValue(currentQuestionIndex);
</script> </script>
{#if currentQuestionIndex === 0} <!-- <svelte:window on:keydown={handleKeydown} /> -->
{#if !preview && currentQuestionIndex > 0}
<Progress value={progressValue} />
{/if}
{#if preview}
{#each questions as currentQuestion, currentQuestionIndex}
{#if currentQuestionIndex === 0}
<div class="mb-2">
<h2 class="my-1">{title}</h2>
<p>{description}</p>
</div>
{:else if !currentQuestion}
<pre>
<code>{JSON.stringify(answers)}</code>
</pre>
{:else if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex)}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex)}
/>
{:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
<TextareaQuestion
{...getRenderProps(currentQuestion, currentQuestionIndex)}
/>
{/if}
{/each}
{:else if currentQuestionIndex === 0}
<div> <div>
<h2 class="my-1">{title}</h2> <h2 class="my-1">{title}</h2>
<p>{description}</p> <p>{description}</p>
@@ -66,27 +122,9 @@
<code>{JSON.stringify(answers)}</code> <code>{JSON.stringify(answers)}</code>
</pre> </pre>
{:else if QUESTION_TYPE.RADIO === currentQuestion.type} {:else if QUESTION_TYPE.RADIO === currentQuestion.type}
<RadioQuestion <RadioQuestion {...renderProps} />
title={currentQuestionIndex + ". " + currentQuestion.title}
name={`${currentQuestion.id}`}
options={currentQuestion.options}
code={currentQuestion.code}
defaultValue={answers[`${currentQuestion.id}`] || false}
{onSubmit}
{onPrevious}
disablePreviousButton={currentQuestionIndex === 1}
isLast={currentQuestionIndex === questions.length}
/>
{:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type} {:else if QUESTION_TYPE.CHECKBOX === currentQuestion.type}
<CheckboxQuestion <CheckboxQuestion {...renderProps} />
title={currentQuestionIndex + ". " + currentQuestion.title} {:else if QUESTION_TYPE.TEXTAREA === currentQuestion.type}
name={`${currentQuestion.id}`} <TextareaQuestion {...renderProps} />
options={currentQuestion.options}
code={currentQuestion.code}
defaultValue={answers[currentQuestion.id] || []}
onSubmit={onCheckboxSubmit}
{onPrevious}
disablePreviousButton={currentQuestionIndex === 1}
isLast={currentQuestionIndex === questions.length}
/>
{/if} {/if}
@@ -20,7 +20,7 @@
label={mode === MODES.edit ? "Save" : "Edit"} label={mode === MODES.edit ? "Save" : "Edit"}
onClick={() => (mode = mode === MODES.edit ? MODES.view : MODES.edit)} onClick={() => (mode = mode === MODES.edit ? MODES.view : MODES.edit)}
/> />
{#if mode === MODES.edit} {#if mode === MODES.edit || preview}
<PrimaryButton <PrimaryButton
variant={VARIANTS.OUTLINED} variant={VARIANTS.OUTLINED}
label={preview ? "Edit" : "Preview"} label={preview ? "Edit" : "Preview"}
@@ -40,7 +40,7 @@
{/if} {/if}
</svelte:fragment> </svelte:fragment>
{#if mode === MODES.edit} {#if mode === MODES.edit && !preview}
<EditMode /> <EditMode />
{:else} {:else}
<ViewMode {preview} /> <ViewMode {preview} />
@@ -31,6 +31,7 @@ export const questionnaire = writable({
value: "Traversy Media", value: "Traversy Media",
}, },
], ],
answers: [],
}, },
{ {
id: "vue-founder", id: "vue-founder",
@@ -59,6 +60,7 @@ export const questionnaire = writable({
value: "Bill Gates", value: "Bill Gates",
}, },
], ],
answers: [],
}, },
{ {
id: "svelte-founder", id: "svelte-founder",
@@ -82,6 +84,7 @@ export const questionnaire = writable({
value: "Coding Train", value: "Coding Train",
}, },
], ],
answers: [2],
}, },
{ {
id: "angular-founder", id: "angular-founder",
@@ -103,6 +106,7 @@ export function handleAddQuestion() {
...QUESTION_TEMPLATE, ...QUESTION_TEMPLATE,
id: questions.length + 1, id: questions.length + 1,
value: "", value: "",
answers: [],
options: [ options: [
{ {
id: 1, id: 1,
@@ -151,11 +155,6 @@ export function handleRemoveOption(questionId, optionId) {
...q, ...q,
questions: questions.map((question) => { questions: questions.map((question) => {
if (question.id === questionId) { if (question.id === questionId) {
console.log(
"filter",
optionId,
question.options.filter((option) => option.id !== optionId)
);
return { return {
...question, ...question,
options: [ options: [
@@ -194,7 +193,7 @@ export function handleCode(questionId, shouldAdd = true) {
if (question.id === questionId) { if (question.id === questionId) {
return { return {
...question, ...question,
code: shouldAdd ? "" : undefined, code: shouldAdd ? (question.code || "") : undefined,
}; };
} }
@@ -203,3 +202,40 @@ export function handleCode(questionId, shouldAdd = true) {
}; };
}); });
} }
export function handleAnswerSelect(questionId, optionId) {
return () => {
questionnaire.update((q) => {
const { questions } = q;
return {
...q,
questions: questions.map((question) => {
if (question.id === questionId) {
const selectOption = question.options.find(option => option.id === optionId);
const newAnswers = [];
if (question.answers.includes(selectOption.id)) {
newAnswers.push(
...question.answers.filter(answer => answer !== selectOption.id)
)
} else {
newAnswers.push(
...question.answers,
selectOption.id
)
}
return {
...question,
answers: newAnswers
};
}
return question;
}),
};
});
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
// border border-gray-300 rounded-md p-3 bg-blue-200 bg-opacity-25 // border border-gray-300 rounded-md p-3 bg-blue-200 bg-opacity-25
</script> </script>
<div class="root mt-4 mx-auto max-w-3xl relative"> <div class="root mt-4 mx-auto max-w-3xl relative pb-5">
{#if $$slots.header} {#if $$slots.header}
<div <div
class="head flex bg-white items-center justify-between sticky right-0 w-full py-3" class="head flex bg-white items-center justify-between sticky right-0 w-full py-3"
+16
View File
@@ -0,0 +1,16 @@
<script>
export let value = 0;
</script>
<div class="w-full h-3 rounded-md bg-gray-300 relative">
<span
class="progress absolute top-0 left-0 rounded-md bg-blue-700 h-full"
style="width: {value}%;"
/>
</div>
<style>
.progress {
transition: width 0.5s linear;
}
</style>
@@ -1,5 +1,6 @@
<script> <script>
import PrimaryButton from "../../PrimaryButton/index.svelte"; import PrimaryButton from "../../PrimaryButton/index.svelte";
import { VARIANTS } from "../../PrimaryButton/constants";
import CodeSnippet from "../../CodeSnippet/index.svelte"; import CodeSnippet from "../../CodeSnippet/index.svelte";
import Checkbox from "../../Form/Checkbox.svelte"; import Checkbox from "../../Form/Checkbox.svelte";
@@ -12,6 +13,7 @@
export let defaultValue = []; export let defaultValue = [];
export let disablePreviousButton = false; export let disablePreviousButton = false;
export let isLast = false; export let isLast = false;
export let isPreview = false;
function getRadioVal(form, name) { function getRadioVal(form, name) {
let values = []; let values = [];
@@ -27,6 +29,7 @@
} }
function handleFormSubmit(event) { function handleFormSubmit(event) {
if (isPreview) return;
const values = getRadioVal(event.target, name); const values = getRadioVal(event.target, name);
onSubmit(name, values); onSubmit(name, values);
event.target.reset(); event.target.reset();
@@ -48,7 +51,7 @@
<div class="ml-4"> <div class="ml-4">
{#each options as option} {#each options as option}
<button <button
class="text-left my-2 border border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full" class="cursor-pointer text-left my-2 border border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full"
type="button" type="button"
> >
<Checkbox <Checkbox
@@ -60,12 +63,20 @@
</button> </button>
{/each} {/each}
</div> </div>
<div class="mt-3 flex items-center justify-between w-full"> {#if !isPreview}
<PrimaryButton <div class="mt-3 flex items-center justify-between w-full">
onClick={handlePrevious} <PrimaryButton
label="Previous" onClick={handlePrevious}
isDisabled={disablePreviousButton} label="Previous"
/> isDisabled={disablePreviousButton}
<PrimaryButton type="submit" label={isLast ? "Finish" : "Next"} {name} /> variant={VARIANTS.OUTLINED}
</div> />
<PrimaryButton
variant={VARIANTS.OUTLINED}
type="submit"
label={isLast ? "Finish" : "Next"}
{name}
/>
</div>
{/if}
</form> </form>
@@ -1,5 +1,6 @@
<script> <script>
import PrimaryButton from "../../PrimaryButton/index.svelte"; import PrimaryButton from "../../PrimaryButton/index.svelte";
import { VARIANTS } from "../../PrimaryButton/constants";
import RadioItem from "../../Form/RadioItem.svelte"; import RadioItem from "../../Form/RadioItem.svelte";
import CodeSnippet from "../../CodeSnippet/index.svelte"; import CodeSnippet from "../../CodeSnippet/index.svelte";
@@ -12,6 +13,7 @@
export let defaultValue = ""; export let defaultValue = "";
export let disablePreviousButton = false; export let disablePreviousButton = false;
export let isLast = false; export let isLast = false;
export let isPreview = false;
function getRadioVal(form, name) { function getRadioVal(form, name) {
let val; let val;
@@ -27,6 +29,7 @@
} }
function handleFormSubmit(event) { function handleFormSubmit(event) {
if (isPreview) return;
const value = getRadioVal(event.target, name); const value = getRadioVal(event.target, name);
onSubmit(name, value); onSubmit(name, value);
event.target.reset(); event.target.reset();
@@ -48,7 +51,7 @@
<div class="ml-4"> <div class="ml-4">
{#each options as option} {#each options as option}
<button <button
class="text-left my-2 border border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full" class="cursor-pointer text-left my-2 border border-gray-300 p-2 rounded-md cursor-pointer hover:bg-gray-200 w-full"
type="button" type="button"
> >
<RadioItem <RadioItem
@@ -60,12 +63,21 @@
</button> </button>
{/each} {/each}
</div> </div>
<div class="mt-3 flex items-center justify-between w-full">
<PrimaryButton {#if !isPreview}
onClick={handlePrevious} <div class="mt-3 flex items-center justify-between w-full">
label="Previous" <PrimaryButton
isDisabled={disablePreviousButton} onClick={handlePrevious}
/> label="Previous"
<PrimaryButton type="submit" label={isLast ? "Finish" : "Next"} {name} /> isDisabled={disablePreviousButton}
</div> variant={VARIANTS.OUTLINED}
/>
<PrimaryButton
variant={VARIANTS.OUTLINED}
type="submit"
label={isLast ? "Finish" : "Next"}
{name}
/>
</div>
{/if}
</form> </form>
@@ -0,0 +1,61 @@
<script>
import PrimaryButton from "../../PrimaryButton/index.svelte";
import { VARIANTS } from "../../PrimaryButton/constants";
import CodeSnippet from "../../CodeSnippet/index.svelte";
import TextArea from "../../Form/TextArea.svelte";
export let title = "";
export let code;
export let name = "";
export let onSubmit = () => {};
export let onPrevious = () => {};
export let defaultValue = "";
export let disablePreviousButton = false;
export let isLast = false;
export let isPreview = false;
function handleFormSubmit(event) {
if (isPreview) return;
onSubmit(name, defaultValue);
event.target.reset();
}
function handlePrevious(event) {
event.preventDefault();
onPrevious();
}
</script>
<form on:submit|preventDefault={handleFormSubmit}>
<h2>{title}</h2>
{#if code}
<CodeSnippet {code} />
{/if}
<div class="ml-4">
<TextArea
bind:value={defaultValue}
rows="5"
placeholder="Write your answer here"
/>
</div>
{#if !isPreview}
<div class="mt-3 flex items-center justify-between w-full">
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={handlePrevious}
label="Previous"
isDisabled={disablePreviousButton}
/>
<PrimaryButton
variant={VARIANTS.OUTLINED}
type="submit"
label={isLast ? "Finish" : "Next"}
{name}
/>
</div>
{/if}
</form>
+1 -1
View File
@@ -22,8 +22,8 @@ export const QUESTION_TYPES = [
export const QUESTION_TEMPLATE = { export const QUESTION_TEMPLATE = {
id: 1, id: 1,
title: "", title: "",
code: "",
type: QUESTION_TYPE.RADIO, type: QUESTION_TYPE.RADIO,
answers: [],
options: [ options: [
{ {
id: 1, id: 1,
+4
View File
@@ -60,4 +60,8 @@
button.active :global(svg.carbon-icon) { button.active :global(svg.carbon-icon) {
fill: var(--main-primary-color); fill: var(--main-primary-color);
} }
button.success :global(svg.carbon-icon) {
fill: green;
}
</style> </style>