From 0da45505d7b5bc4d9b1e4aa1e9489f8b1f165725 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Mon, 4 Jan 2021 04:00:10 +0300 Subject: Adds Question Rendering Adds a question component, and calls it on form page. Adds styling for input types and form page. Lays foundation for validation and submission. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/Question.tsx | 128 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/components/Question.tsx (limited to 'src/components/Question.tsx') diff --git a/src/components/Question.tsx b/src/components/Question.tsx new file mode 100644 index 0000000..1c0fb31 --- /dev/null +++ b/src/components/Question.tsx @@ -0,0 +1,128 @@ +/** @jsx jsx */ +import { jsx } from "@emotion/react"; +import React, { ChangeEvent } from "react"; + +import { Question, QuestionType } from "../api/question"; +import create_input from "./InputTypes"; + +const _skip_normal_state: Array = [ + QuestionType.Radio, + QuestionType.Checkbox, + QuestionType.Select, + QuestionType.Section, + QuestionType.Range +]; + +export type QuestionProp = { + question: Question, + public_state: Map, +} + +class RenderedQuestion extends React.Component { + constructor(props: QuestionProp) { + super(props); + if (props.question.type === QuestionType.TextArea) { + this.handler = this.text_area_handler.bind(this); + } else { + this.handler = this.normal_handler.bind(this); + } + + if (!_skip_normal_state.includes(props.question.type)) { + this._setState("value", ""); + } + } + + _setState(target: string, value: string | boolean | null, callback?:() => void): void { + this.setState({[target]: value}, callback); + this.props.public_state.set(target, value); + } + + handler(_: ChangeEvent): void {} // eslint-disable-line + + normal_handler(event: ChangeEvent): void { + let target: string; + let value: string | boolean; + + switch (event.target.type) { + case QuestionType.Checkbox: + target = this.props.question.id; + value = event.target.checked; + break; + + case QuestionType.Radio: + target = "value"; + if (event.target.parentElement) { + value = event.target.parentElement.innerText.trimEnd(); + } else { + value = event.target.value; + } + break; + + case QuestionType.Select: + // Handled by component + return; + + default: + target = "value"; + value = event.target.value; + } + + this._setState(target, value); + + // Toggle checkbox class + if (event.target.type == "checkbox" && event.target.parentElement !== null) { + event.target.parentElement.classList.toggle("unselected_checkbox_label"); + event.target.parentElement.classList.toggle("selected_checkbox_label"); + } + } + + text_area_handler(event: ChangeEvent): void { + this._setState("value", event.target.value); + } + + componentDidMount(): void { + // Initialize defaults for complex and nested fields + const options: string | string[] = this.props.question.data["options"]; + + if (this.props.public_state.size === 0) { + switch (this.props.question.type) { + case QuestionType.Checkbox: + if (typeof options === "string") { + return; + } + + options.forEach((option, index) => { + this._setState(`${("000" + index).slice(-4)}. ${option}`, false); + }); + break; + + case QuestionType.Range: + case QuestionType.Radio: + case QuestionType.Select: + this._setState("value", null); + break; + } + } + } + + render(): JSX.Element { + const question = this.props.question; + + if (question.type === QuestionType.Section) { + return
+

{question.name}

+ { question.data["text"] ?

{question.data["text"]}

: "" } +
+
; + } else { + return
+

+ {question.name}* +

+ { create_input(this.props, this.handler) }
+
; + } + } +} + +export default RenderedQuestion; -- cgit v1.2.3 From 996c14afb9d81e962ef66b99bd869bce4f3688f0 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Wed, 6 Jan 2021 08:00:31 +0300 Subject: Breaks Up CSS Into Components Moves the styles from the CSS file, into emotion CSS in each component's file to make navigation easier, and keep CSS and JSX together.Drops raw-loader dependency. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- package.json | 1 - src/colors.ts | 2 + src/commonStyles.tsx | 39 +++ src/components/InputTypes/Checkbox.tsx | 62 ++++- src/components/InputTypes/Radio.tsx | 4 +- src/components/InputTypes/Range.tsx | 101 ++++++- src/components/InputTypes/Select.tsx | 109 +++++++- src/components/InputTypes/ShortText.tsx | 3 +- src/components/InputTypes/TextArea.tsx | 14 +- src/components/Question.tsx | 61 ++++- src/components/ScrollToTop.tsx | 3 +- src/pages/FormPage.tsx | 168 ++++++++++-- src/pages/css/FormPage.css | 452 -------------------------------- src/tests/pages/FormPage.test.tsx | 2 +- webpack.config.js | 6 - yarn.lock | 8 - 16 files changed, 498 insertions(+), 537 deletions(-) create mode 100644 src/commonStyles.tsx delete mode 100644 src/pages/css/FormPage.css (limited to 'src/components/Question.tsx') diff --git a/package.json b/package.json index e74aa57..c3e21ce 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "fs-extra": "9.0.1", "html-webpack-plugin": "4.5.1", "identity-obj-proxy": "3.0.0", - "raw-loader": "4.0.2", "react": "17.0.1", "react-app-polyfill": "2.0.0", "react-dom": "17.0.1", diff --git a/src/colors.ts b/src/colors.ts index e9c74b1..52b48cb 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -1,8 +1,10 @@ export default { blurple: "#7289DA", + darkerBlurple: "#4E609C", darkButNotBlack: "#2C2F33", notQuiteBlack: "#23272A", greyple: "#99AAB5", + darkerGreyple: "#6E7D88", error: "#f04747", success: "#43b581" }; diff --git a/src/commonStyles.tsx b/src/commonStyles.tsx new file mode 100644 index 0000000..d47dea7 --- /dev/null +++ b/src/commonStyles.tsx @@ -0,0 +1,39 @@ +import { css } from "@emotion/react"; + +const selectable = css` + -moz-user-select: text; + -webkit-user-select: text; + -ms-user-select: text; + user-select: text; +`; + +const unselectable = css` + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +`; + +const textInputs = css` + display: inline-block; + width: min(20rem, 90%); + height: 100%; + min-height: 2rem; + + background: whitesmoke; + + color: black; + padding: 0 1rem; + font: inherit; + + margin-bottom: 0; + + border: 0.1rem solid black; + border-radius: 8px; +`; + +export { + selectable, + unselectable, + textInputs +}; diff --git a/src/components/InputTypes/Checkbox.tsx b/src/components/InputTypes/Checkbox.tsx index ed02b83..07872d6 100644 --- a/src/components/InputTypes/Checkbox.tsx +++ b/src/components/InputTypes/Checkbox.tsx @@ -1,6 +1,7 @@ /** @jsx jsx */ -import { jsx } from "@emotion/react"; +import { jsx, css } from "@emotion/react"; import React, { ChangeEvent } from "react"; +import colors from "../../colors"; interface CheckboxProps { index: number, @@ -8,13 +9,66 @@ interface CheckboxProps { handler: (event: ChangeEvent) => void } +const generalStyles = css` + label { + display: inline-block; + position: relative; + top: 0.25em; + + width: 1em; + height: 1em; + + margin: 1rem 0.5rem 0 0; + border: whitesmoke 0.2rem solid; + border-radius: 25%; + + transition: background-color 300ms; + } + + .unselected { + background-color: white; + } + + .unselected:hover { + background-color: lightgray; + } + + input { + position: absolute; + opacity: 0; + height: 0; + width: 0; + } + + .checkmark { + position: absolute; + } +`; + +const activeStyles = css` + .selected { + background-color: ${colors.blurple}; + } + + .selected .checkmark { + width: 0.30rem; + height: 0.60rem; + left: 0.25em; + + border: solid white; + border-width: 0 0.2rem 0.2rem 0; + + transform: rotate(45deg); + } +`; + export default function Checkbox(props: CheckboxProps): JSX.Element { return ( -