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> = [
+    QuestionType.Radio,
+    QuestionType.Checkbox,
+    QuestionType.Select,
+    QuestionType.Section,
+    QuestionType.Range
+];
+
+export type QuestionProp = {
+    question: Question,
+    public_state: Map<string, string | boolean | null>,
+}
+
+class RenderedQuestion extends React.Component<QuestionProp> {
+    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<HTMLInputElement | HTMLTextAreaElement>): void {} // eslint-disable-line
+
+    normal_handler(event: ChangeEvent<HTMLInputElement>): 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<HTMLTextAreaElement>): 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 <div>
+                <h1 className="selectable">{question.name}</h1>
+                { question.data["text"] ? <h3 className="selectable">{question.data["text"]}</h3> : "" }
+                <hr className="section_header"/>
+            </div>;
+        } else {
+            return <div>
+                <h2 className="selectable">
+                    {question.name}<span id={question.required ? "required" : ""} className="required_star">*</span>
+                </h2>
+                { create_input(this.props, this.handler) }<hr/>
+            </div>;
+        }
+    }
+}
+
+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<HTMLInputElement>) => 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 (
-        <label>
-            <label className="unselected_checkbox_label checkbox_label unselectable">
+        <label css={[generalStyles, activeStyles]}>
+            <label className="unselected">
                 <input type="checkbox" value={props.option}
                     name={`${("000" + props.index).slice(-4)}. ${props.option}`} onChange={props.handler}/>
-                <span className="checkmark_span"/>
+                <span className="checkmark"/>
             </label>
             {props.option}<br/>
         </label>
diff --git a/src/components/InputTypes/Radio.tsx b/src/components/InputTypes/Radio.tsx
index 81f8375..be0f60e 100644
--- a/src/components/InputTypes/Radio.tsx
+++ b/src/components/InputTypes/Radio.tsx
@@ -1,5 +1,5 @@
 /** @jsx jsx */
-import { jsx } from "@emotion/react";
+import { jsx, css } from "@emotion/react";
 import React, { ChangeEvent } from "react";
 
 interface RadioProps {
@@ -11,7 +11,7 @@ interface RadioProps {
 export default function Radio(props: RadioProps): JSX.Element {
     return (
         <label>
-            <input type="radio" name={props.question_id} className="radio" onChange={props.handler}/>
+            <input type="radio" name={props.question_id} css={css`margin: 1rem 0.5rem 0 0;`} onChange={props.handler}/>
             {props.option}<br/>
         </label>
     );
diff --git a/src/components/InputTypes/Range.tsx b/src/components/InputTypes/Range.tsx
index a0260ad..af46b05 100644
--- a/src/components/InputTypes/Range.tsx
+++ b/src/components/InputTypes/Range.tsx
@@ -1,6 +1,7 @@
 /** @jsx jsx */
-import { jsx } from "@emotion/react";
+import { jsx, css } from "@emotion/react";
 import React from "react";
+import colors from "../../colors";
 
 interface RangeProps {
     question_id: string,
@@ -8,13 +9,12 @@ interface RangeProps {
     state_dict: Map<string, string | boolean | null>
 }
 
-let last_selection: Element;
-
 interface handler_props {
     state_dict: Map<string, string | boolean | null>,
     ref: React.RefObject<HTMLLabelElement>
 }
 
+let last_selection: Element;
 function handler(this: handler_props): void {
     if (last_selection) {
         last_selection.classList.toggle("selected");
@@ -29,23 +29,108 @@ function handler(this: handler_props): void {
     this.state_dict.set("value", value);
 }
 
+const containerStyles = css`
+  display: flex;
+  justify-content: space-between;
+  position: relative;
+  width: 100%;
+
+  @media (max-width: 800px) {
+    width: 20%;
+    display: block;
+    margin: 0 auto;
+    
+    label span {
+      margin-left: 0;
+      transform: translateY(1.6rem) translateX(2rem);
+    }
+  }
+`;
+
+const optionStyles = css`
+  display: inline-block;
+  transform: translateX(-50%);
+  margin: 0 50%;
+
+  white-space: nowrap;
+
+  transition: transform 300ms;
+`;
+
+const rangeDotStyles = css`
+  .range_dot {
+    width: 0.8rem;
+    height: 0.8rem;
+    background-color: whitesmoke;
+
+    border: 0.2rem solid whitesmoke;
+    border-radius: 50%;
+
+    transition: background-color 300ms;
+  }
+
+  .range_dot.selected {
+    background-color: ${colors.blurple};
+  }
+
+  @media (max-width: 800px) {
+    .range_dot {
+      margin-bottom: 1.5rem;
+    }
+  }
+`;
+
+const sliderContainerStyles = css`
+  display: flex;
+  justify-content: center;
+  width: 100%;
+
+  position: absolute;
+  z-index: -1;
+
+  top: 2rem;
+
+  transition: all 300ms;
+  
+  @media (max-width: 800px) {
+    width: 0.5rem;
+    height: 88%;
+
+    left: 0.32rem;
+
+    background: whitesmoke;
+  }
+`;
+
+const sliderStyles = css`
+  width: 98%; /* Needs to be slightly smaller than container to work on all devices */
+  height: 0.5rem;
+  background-color: whitesmoke;
+
+  transition: transform 300ms;
+  
+  @media (max-width: 800px) {
+    display: none;
+  }
+`;
+
 export default function Range(props: RangeProps): JSX.Element {
     const range = props.options.map((option, index) => {
         const ref: React.RefObject<HTMLLabelElement> = React.createRef();
         return (
-            <label key={index} ref={ref} onClick={handler.bind({state_dict: props.state_dict, ref: ref})}>
-                <span>{option}</span>
+            <label key={index} ref={ref} css={css`width: 1rem;`} onClick={handler.bind({state_dict: props.state_dict, ref})}>
+                <span css={optionStyles}>{option}</span>
                 <div className="range_dot"/>
             </label>
         );
     });
 
     return (
-        <div className="range">
+        <div css={[containerStyles, rangeDotStyles]}>
             { range }
 
-            <div className="range_slider_container">
-                <div className="range_slider"/>
+            <div css={sliderContainerStyles}>
+                <div css={sliderStyles}/>
             </div>
         </div>
     );
diff --git a/src/components/InputTypes/Select.tsx b/src/components/InputTypes/Select.tsx
index 355f5d0..de763bf 100644
--- a/src/components/InputTypes/Select.tsx
+++ b/src/components/InputTypes/Select.tsx
@@ -1,5 +1,5 @@
 /** @jsx jsx */
-import { jsx } from "@emotion/react";
+import { jsx, css } from "@emotion/react";
 import React from "react";
 
 interface SelectProps {
@@ -12,11 +12,86 @@ interface HandlerProps {
     ref: React.RefObject<HTMLDivElement>
 }
 
-class Select extends React.Component<SelectProps> {
-    constructor(props: SelectProps) {
-        super(props);
-    }
+const containerStyles = css`
+  .container {
+    display: inline-block;
+    position: relative;
+
+    width: min(20rem, 90%);
+    height: 100%;
+    min-height: 2rem;
+
+    background: whitesmoke;
+
+    color: black;
+    text-align: center;
+
+    margin-bottom: 0;
+
+    border: 0.1rem solid black;
+    border-radius: 8px;
+
+    transition: border-radius 400ms;
+  }
+
+  .container.active {
+    height: auto;
+    border-radius: 8px 8px 0 0;
+  }
+`;
+
+const arrowStyles = css`
+  .arrow {
+    display: inline-block;
+    height: 0.5rem;
+    width: 0.5rem;
+
+    position: relative;
+    float: right;
+    right: 1em;
+    top: 0.7rem;
+
+    border: solid black;
+    border-width: 0 0.2rem 0.2rem 0;
 
+    transform: rotate(45deg);
+    transition: transform 400ms;
+  }
+
+  .active .arrow {
+    transform: translateY(40%) rotate(225deg);
+  }
+`;
+
+const optionContainer = css`
+  .option_container {
+    display: block;
+    position: absolute;
+    width: 100%;
+
+    /* Need to account for margin */
+    left: -0.1rem;
+
+    visibility: hidden;
+    opacity: 0;
+
+    background: whitesmoke;
+    overflow: hidden;
+
+    border: 0.1rem solid black;
+    border-radius: 0 0 8px 8px;
+    border-top: none;
+
+    transition: opacity 400ms, visibility 400ms;
+  }
+
+  .active .option_container {
+    visibility: visible;
+    opacity: 1;
+  }
+`;
+
+class Select extends React.Component<SelectProps> {
     click_handler(this: HandlerProps, event: React.MouseEvent<HTMLDivElement, MouseEvent>): void {
         if (!this.ref.current) {
             return;
@@ -43,18 +118,24 @@ class Select extends React.Component<SelectProps> {
     render(): JSX.Element {
         const container_ref: React.RefObject<HTMLDivElement> = React.createRef();
 
-        return (
-            <div className="select_container" ref={container_ref} onClick={this.click_handler.bind({ref: container_ref, props: this.props})}>
-                <span className="select_arrow"/>
-                <span className="selected_option">...</span>
-                <div className="select_options_container">
-                    <div className="select_options">
-                        { this.props.options.map((option, index) => <div key={index}><hr/><div id="option">{option}</div></div>) }
-                    </div>
+        const element: JSX.Element = (
+            <div className="container" ref={container_ref} onClick={this.click_handler.bind({ref: container_ref, props: this.props})}>
+                <span className="arrow"/>
+                <span className="selected_option" css={css`display: block; padding: 0.5rem 0;`}>...</span>
+
+                <div className="option_container">
+                    { this.props.options.map((option, index) => (
+                        <div css={css`:hover { background-color: lightgray; }`} key={index}>
+                            <hr css={css`margin: 0 1rem;`}/>
+                            <div id="option" css={css`padding: 0.75rem;`}>{option}</div>
+                        </div>
+                    )) }
                 </div>
             </div>
         );
+
+        return <div css={[containerStyles, arrowStyles, optionContainer]}>{ element }</div>;
     }
 }
 
-export default Select;
\ No newline at end of file
+export default Select;
diff --git a/src/components/InputTypes/ShortText.tsx b/src/components/InputTypes/ShortText.tsx
index 182571a..1e38bcd 100644
--- a/src/components/InputTypes/ShortText.tsx
+++ b/src/components/InputTypes/ShortText.tsx
@@ -1,11 +1,12 @@
 /** @jsx jsx */
 import { jsx } from "@emotion/react";
 import React, { ChangeEvent } from "react";
+import { textInputs } from "../../commonStyles";
 
 interface ShortTextProps {
     handler: (event: ChangeEvent<HTMLInputElement>) => void
 }
 
 export default function ShortText(props: ShortTextProps): JSX.Element {
-    return <input type="text" className="short_text" placeholder="Enter Text..." onChange={props.handler}/>;
+    return <input type="text" css={textInputs} placeholder="Enter Text..." onChange={props.handler}/>;
 }
diff --git a/src/components/InputTypes/TextArea.tsx b/src/components/InputTypes/TextArea.tsx
index 550adea..2b7a5f6 100644
--- a/src/components/InputTypes/TextArea.tsx
+++ b/src/components/InputTypes/TextArea.tsx
@@ -1,11 +1,21 @@
 /** @jsx jsx */
-import { jsx } from "@emotion/react";
+import { jsx, css } from "@emotion/react";
 import React, { ChangeEvent } from "react";
+import { textInputs } from "../../commonStyles";
 
 interface TextAreaProps {
     handler: (event: ChangeEvent<HTMLTextAreaElement>) => void
 }
 
+const styles = css`
+  min-height: 20rem;
+  min-width: 40%;
+  width: 100%;
+  box-sizing: border-box;
+
+  padding: 1rem;
+`;
+
 export default function TextArea(props: TextAreaProps): JSX.Element {
-    return <textarea className="text_area" placeholder="Enter Text..." onChange={props.handler}/>;
+    return <textarea css={[textInputs, styles]} placeholder="Enter Text..." onChange={props.handler}/>;
 }
diff --git a/src/components/Question.tsx b/src/components/Question.tsx
index 1c0fb31..66c1668 100644
--- a/src/components/Question.tsx
+++ b/src/components/Question.tsx
@@ -1,8 +1,9 @@
 /** @jsx jsx */
-import { jsx } from "@emotion/react";
+import { jsx, css } from "@emotion/react";
 import React, { ChangeEvent } from "react";
 
 import { Question, QuestionType } from "../api/question";
+import { selectable } from "../commonStyles";
 import create_input from "./InputTypes";
 
 const _skip_normal_state: Array<QuestionType> = [
@@ -37,6 +38,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
         this.props.public_state.set(target, value);
     }
 
+    // This is here to allow dynamic selection between the general handler, and the textarea handler.
     handler(_: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void {} // eslint-disable-line
 
     normal_handler(event: ChangeEvent<HTMLInputElement>): void {
@@ -71,8 +73,8 @@ class RenderedQuestion extends React.Component<QuestionProp> {
 
         // 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");
+            event.target.parentElement.classList.toggle("unselected");
+            event.target.parentElement.classList.toggle("selected");
         }
     }
 
@@ -109,17 +111,56 @@ class RenderedQuestion extends React.Component<QuestionProp> {
         const question = this.props.question;
 
         if (question.type === QuestionType.Section) {
-            return <div>
-                <h1 className="selectable">{question.name}</h1>
-                { question.data["text"] ? <h3 className="selectable">{question.data["text"]}</h3> : "" }
-                <hr className="section_header"/>
+            const styles = css`
+              h1 {
+                margin-bottom: 0;
+              }
+
+              h3 {
+                margin-top: 0;
+              }
+
+              h1, h3 {
+                text-align: center;
+                padding: 0 2rem;
+              }
+
+              @media (max-width: 500px) {
+                h1, h3 {
+                  padding: 0;
+                }
+              }
+            `;
+
+            return <div css={styles}>
+                <h1 css={selectable}>{question.name}</h1>
+                { question.data["text"] ? <h3 css={selectable}>{question.data["text"]}</h3> : "" }
+                <hr css={css`color: gray; margin: 3rem 0;`}/>
             </div>;
+
         } else {
+            const requiredStarStyles = css`
+              span {
+                display: none;
+              }
+
+              .required {
+                display: inline-block;
+                position: relative;
+
+                color: red;
+
+                top: -0.2rem;
+                margin-left: 0.2rem;
+              }
+            `;
+
             return <div>
-                <h2 className="selectable">
-                    {question.name}<span id={question.required ? "required" : ""} className="required_star">*</span>
+                <h2 css={[selectable, requiredStarStyles]}>
+                    {question.name}<span className={question.required ? "required" : ""}>*</span>
                 </h2>
-                { create_input(this.props, this.handler) }<hr/>
+                { create_input(this.props, this.handler) }
+                <hr css={css`color: gray; margin: 3rem 0;`}/>
             </div>;
         }
     }
diff --git a/src/components/ScrollToTop.tsx b/src/components/ScrollToTop.tsx
index 6af938d..4888dec 100644
--- a/src/components/ScrollToTop.tsx
+++ b/src/components/ScrollToTop.tsx
@@ -1,6 +1,7 @@
 /** @jsx jsx */
 import { jsx, css } from "@emotion/react";
 import React from "react";
+import colors from "../colors";
 
 const styles = css`
   width: 2.5rem;
@@ -10,7 +11,7 @@ const styles = css`
   bottom: 3rem;
   right: 3rem;
   
-  background-color: #7289DA; /* Blurple */
+  background-color: ${colors.blurple};
   border-radius: 50%;
 
   opacity: 0;
diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx
index b966e84..647003f 100644
--- a/src/pages/FormPage.tsx
+++ b/src/pages/FormPage.tsx
@@ -11,12 +11,143 @@ import Loading from "../components/Loading";
 import ScrollToTop from "../components/ScrollToTop";
 
 import { Form, FormFeatures, getForm } from "../api/forms";
+import colors from "../colors";
+import { unselectable }  from "../commonStyles";
 
 
 interface PathParams {
     id: string
 }
 
+interface NavigationProps {
+    form_state: boolean  // Whether the form is open or not
+}
+
+class Navigation extends React.Component<NavigationProps> {
+    containerStyles = css`
+      margin: auto;
+      width: 50%;
+
+      text-align: center;
+      font-size: 1.5rem;
+      white-space: nowrap;
+
+      > div {
+        display: inline-block;
+        margin: 2rem auto;
+        width: 50%;
+      }
+
+      @media (max-width: 850px) {
+        width: 100%;
+
+        > div {
+          display: flex;
+          justify-content: center;
+
+          margin: 0 auto;
+        }
+      }
+
+      .return_button {
+        text-align: left;
+      }
+
+      .return_button.closed {
+        text-align: center;
+      }
+    `;
+
+    separatorStyles = css`
+      height: 0;
+      display: none;
+
+      @media (max-width: 850px) {
+        display: block;
+      }
+    `;
+
+    returnStyles = css`
+      padding: 0.5rem 2rem;
+      border-radius: 8px;
+
+      color: white;
+      text-decoration: none;
+
+      background-color: ${colors.greyple};
+      transition: background-color 300ms;
+
+      :hover {
+        background-color: ${colors.darkerGreyple};
+      }
+    }
+    `;
+
+    submitStyles = css`
+      text-align: right;
+
+      button {
+        padding: 0.5rem 4rem;
+        cursor: pointer;
+
+        border: none;
+        border-radius: 8px;
+
+        color: white;
+        font: inherit;
+
+        background-color: ${colors.blurple};
+        transition: background-color 300ms;
+      }
+
+      button:hover {
+        background-color: ${colors.darkerBlurple};
+      }
+    `;
+
+    render(): JSX.Element {
+        let submit = null;
+        if (this.props.form_state) {
+            submit = (
+                <div css={this.submitStyles}>
+                    <button form="form" type="submit">Submit</button>
+                </div>
+            );
+        }
+
+        return (
+            <div css={[unselectable, this.containerStyles]}>
+                <div className={ "return_button" + (this.props.form_state ? "" : " closed") }>
+                    <Link to="/" css={this.returnStyles}>Return Home</Link>
+                </div>
+                <br css={this.separatorStyles}/>
+                { submit }
+            </div>
+        );
+    }
+}
+
+const formStyles = css`
+  margin: auto;
+  width: 50%;
+
+  @media (max-width: 800px) {
+    /* Make form larger on mobile and tablet screens */
+    width: 80%;
+  }
+`;
+
+const closedHeaderStyles = css`
+  margin-bottom: 2rem;
+  padding: 1rem 4rem;
+  border-radius: 8px;
+
+  text-align: center;
+  font-size: 1.5rem;
+
+  background-color: ${colors.error};
+`;
+
 function FormPage(): JSX.Element {
     const { id } = useParams<PathParams>();
 
@@ -33,7 +164,7 @@ function FormPage(): JSX.Element {
     }
 
     const questions = form.questions.map((question, index) => {
-        return <RenderedQuestion question={question} public_state={new Map()} key={index}/>;
+        return <RenderedQuestion question={question} public_state={new Map()} key={index + Date.now()}/>;
     });
 
     function handleSubmit(event: SyntheticEvent) {
@@ -53,39 +184,22 @@ function FormPage(): JSX.Element {
     const open: boolean = form.features.includes(FormFeatures.Open);
 
     let closed_header = null;
-    let submit = null;
-
-    if (open) {
-        submit = (
-            <div className="submit_form">
-                <button form="form" type="submit">Submit</button>
-            </div>
-        );
-    } else {
-        closed_header = (
-            <div className="closed_header">
-                <div>This form is now closed. You will not be able to submit your response.</div>
-            </div>
-        );
+    if (!open) {
+        closed_header = <div css={closedHeaderStyles}>This form is now closed. You will not be able to submit your response.</div>;
     }
 
-
     return (
         <div>
-            <HeaderBar title={form.name} description={form.description} key={2}/>
-            <div css={css`${require("./css/FormPage.css")};`}>
-                <form id="form" onSubmit={handleSubmit} className="unselectable">
+            <HeaderBar title={form.name} description={form.description}/>
+
+            <div>
+                <form id="form" onSubmit={handleSubmit} css={[formStyles, unselectable]}>
                     { closed_header }
-                    {questions}
+                    { questions }
                 </form>
-                <div className="nav unselectable">
-                    <div className={ "nav_buttons" + (open ? "" : " closed") }>
-                        <Link to="/" className="return_home">Return Home</Link>
-                    </div>
-                    <br className="nav_separator"/>
-                    { submit }
-                </div>
+                <Navigation form_state={open}/>
             </div>
+
             <div css={css`margin-bottom: 10rem`}/>
             <ScrollToTop/>
         </div>
diff --git a/src/pages/css/FormPage.css b/src/pages/css/FormPage.css
deleted file mode 100644
index 254ddef..0000000
--- a/src/pages/css/FormPage.css
+++ /dev/null
@@ -1,452 +0,0 @@
-form {
-    margin: auto;
-    width: 50%;
-}
-
-@media (max-width: 800px) {
-    /* Make form larger on mobile and tablet screens */
-    form {
-        width: 80%;
-    }
-}
-
-hr {
-    color: gray;
-    margin: 3rem 0;
-}
-
-h1 {
-    font-size: 2.5rem;
-    margin-bottom: 0;
-    text-align: center;
-}
-
-h3 {
-    margin-top: 0;
-    text-align: center;
-}
-
-.section_header {
-    margin-top: 1rem;
-}
-
-.closed_header {
-    margin-bottom: 2rem;
-    text-align: center;
-}
-
-.closed_header div {
-    font-size: 1.5rem;
-    background-color: #f04747;
-
-    padding: 1rem 4rem;
-    border-radius: 8px;
-}
-
-.unselectable {
-    -moz-user-select: none;
-    -webkit-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-}
-
-.selectable {
-    -moz-user-select: text;
-    -webkit-user-select: text;
-    -ms-user-select: text;
-    user-select: text;
-}
-
-/* ------------------------------------------------------------- */
-/*                           Required                            */
-/* ------------------------------------------------------------- */
-.required_star {
-    display: none;
-}
-
-#required.required_star {
-    display: inline-block;
-    position: relative;
-
-    color: red;
-
-    top: -0.2rem;
-    margin-left: 0.2rem;
-}
-
-/* ------------------------------------------------------------- */
-/*                          Checkboxes                           */
-/* ------------------------------------------------------------- */
-.checkbox_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%;
-
-    -webkit-transition: background-color 300ms ease-in-out;
-    transition: background-color 300ms ease-in-out;
-}
-
-.checkbox_label input {
-    position: absolute;
-    opacity: 0;
-    height: 0;
-    width: 0;
-}
-
-.unselected_checkbox_label {
-    background-color: white;
-}
-
-.unselected_checkbox_label:hover {
-    background-color: lightgray;
-}
-
-.selected_checkbox_label {
-    background-color: #7289DA; /* Blurple */
-}
-
-.checkmark_span {
-    position: absolute;
-}
-
-.selected_checkbox_label .checkmark_span {
-    width: 0.30rem;
-    height: 0.60rem;
-    left: 0.25em;
-
-    border: solid white;
-    border-width: 0 0.2rem 0.2rem 0;
-
-    -webkit-transform: rotate(45deg);
-    -ms-transform: rotate(45deg);
-    transform: rotate(45deg);
-}
-
-/* ------------------------------------------------------------- */
-/*                             Radio                             */
-/* ------------------------------------------------------------- */
-input[type="radio"] {
-    margin: 1rem 0.5rem 0 0;
-}
-
-/* ------------------------------------------------------------- */
-/*                            Select                             */
-/* ------------------------------------------------------------- */
-.select_container {
-    display: inline-block;
-    position: relative;
-
-    width: min(20rem, 90%);
-    height: 100%;
-    min-height: 2rem;
-
-    background: whitesmoke;
-
-    color: black;
-    text-align: center;
-
-    margin-bottom: 0;
-
-    border: 0.1rem solid black;
-    border-radius: 8px;
-
-    -webkit-transition: border-radius 400ms;
-    transition: border-radius 400ms;
-}
-
-.select_container.active {
-    height: auto;
-    border-radius: 8px 8px 0 0;
-}
-
-.select_arrow {
-    display: inline-block;
-    height: 0.5rem;
-    width: 0.5rem;
-
-    position: relative;
-    float: right;
-    right: 1em;
-    top: 0.7rem;
-
-    border: solid black;
-    border-width: 0 0.2rem 0.2rem 0;
-
-    -webkit-transform: rotate(45deg);
-    -ms-transform: rotate(45deg);
-    transform: rotate(45deg);
-
-    -webkit-transition: transform 400ms;
-    transition: transform 400ms;
-}
-
-.select_container.active .select_arrow {
-    -webkit-transform: translateY(40%) rotate(225deg);
-    -ms-transform: translateY(40%) rotate(225deg);
-    transform: translateY(40%) rotate(225deg);
-}
-
-.selected_option {
-    display: block;
-    padding: 0.5rem 0;
-}
-
-.select_options_container {
-    position: relative;
-    width: 100%;
-
-    /* Need to account for margin */
-    left: -0.1rem;
-}
-
-.select_options {
-    display: block;
-    position: absolute;
-    width: 100%;
-
-    visibility: hidden;
-    opacity: 0;
-
-    background: whitesmoke;
-    overflow: hidden;
-
-    border: 0.1rem solid black;
-    border-radius: 0 0 8px 8px;
-    border-top: none;
-
-    -webkit-transition: opacity 400ms, visibility 400ms;
-    transition: opacity 400ms, visibility 400ms;
-}
-
-.select_container.active .select_options {
-    visibility: visible;
-    opacity: 1;
-}
-
-.select_options > div > div {
-    padding: 0.75rem;
-}
-
-.select_options > div:hover {
-    background-color: lightgray;
-}
-
-.select_options hr {
-    margin: 0 1rem;
-}
-
-/* ------------------------------------------------------------- */
-/*                          Text Types                           */
-/* ------------------------------------------------------------- */
-.short_text, .text_area {
-    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;
-}
-
-.text_area {
-    min-height: 20rem;
-    min-width: 40%;
-    width: 100%;
-    box-sizing: border-box;
-
-    padding: 1rem;
-}
-
-/* ------------------------------------------------------------- */
-/*                            Range                              */
-/* ------------------------------------------------------------- */
-.range {
-    display: flex;
-    justify-content: space-between;
-    position: relative;
-    width: 100%;
-}
-
-.range label {
-    width: 1rem;
-}
-
-.range label span {
-    display: inline-block;
-    transform: translateX(-50%);
-    margin: 0 50%;
-
-    white-space: nowrap;
-
-    transition: transform 300ms;
-}
-
-.range_dot {
-    width: 0.8rem;
-    height: 0.8rem;
-    background-color: whitesmoke;
-
-    border: 0.2rem solid whitesmoke;
-    border-radius: 50%;
-
-    transition: background-color 300ms;
-}
-
-.range_dot.selected {
-    background-color: #7289DA; /* Blurple */
-}
-
-.range_slider_container {
-    display: flex;
-    justify-content: center;
-    width: 100%;
-
-    position: absolute;
-    z-index: -1;
-
-    top: 2rem;
-
-    transition: all 300ms;
-}
-
-.range_slider {
-    width: 98%; /* Needs to be slightly smaller than container to work on all devices */
-    height: 0.5rem;
-    background-color: whitesmoke;
-
-    transition: transform 300ms;
-}
-
-/* ------------------------------------------------------------- */
-/*                         Mobile Range                          */
-/* ------------------------------------------------------------- */
-@media (max-width: 800px){
-    .range {
-        width: 20%;
-        display: block;
-        margin: 0 auto;
-    }
-
-    .range_dot {
-        margin-bottom: 1.5rem;
-    }
-
-    .range label span {
-        margin-left: 0;
-        transform: translateY(1.6rem) translateX(2rem);
-    }
-
-    .range_slider_container {
-        width: 0.5rem;
-        left: 0.32rem;
-        height: 88%;
-
-        background: whitesmoke;
-        z-index: -1;
-    }
-
-    .range_slider {
-        display: none;
-    }
-}
-
-/* ------------------------------------------------------------- */
-/*                          Navigation                           */
-/* ------------------------------------------------------------- */
-.nav {
-    margin: auto;
-    width: 50%;
-
-    text-align: center;
-    font-size: 1.5rem;
-    white-space: nowrap;
-}
-
-.nav_separator {
-    height: 0;
-    display: none;
-}
-
-.nav > div {
-    display: inline-block;
-    margin: 2rem auto;
-    width: 50%;
-}
-
-.nav_buttons {
-    text-align: left;
-}
-
-.nav_buttons.closed {
-    text-align: center;
-}
-
-.submit_form {
-    text-align: right;
-}
-
-/* Tile Buttons Vertically On Smaller Devices */
-@media (max-width: 850px) {
-    .nav {
-        width: 100%;
-    }
-
-    .nav_separator {
-        display: block;
-    }
-
-    .nav > div {
-        display: flex;
-        justify-content: center;
-
-        margin: 0 auto;
-    }
-}
-
-.return_home {
-    padding: 0.5rem 2rem;
-    border-radius: 8px;
-
-    color: white;
-    text-decoration: none;
-
-    background-color: #99AAB5; /* Gray-ish */
-    transition: background-color 300ms;
-}
-
-.return_home:hover {
-    background-color: #6E7D88; /* Darker gray-ish */
-}
-
-.submit_form button {
-    padding: 0.5rem 4rem;
-    cursor: pointer;
-
-    border: none;
-    border-radius: 8px;
-
-    color: white;
-    font: inherit;
-
-    background-color: #7289DA; /* Blurple */
-    transition: background-color 300ms;
-}
-
-.submit_form button:hover {
-    background-color: #4E609C; /* Darker blurple */
-}
diff --git a/src/tests/pages/FormPage.test.tsx b/src/tests/pages/FormPage.test.tsx
index f7ecc32..62577cd 100644
--- a/src/tests/pages/FormPage.test.tsx
+++ b/src/tests/pages/FormPage.test.tsx
@@ -27,6 +27,6 @@ test("calls api method to load form", () => {
     Object.defineProperty(forms, "getForm", {value: jest.fn(oldImpl)});
 
     render(<Router><Route history={history}><FormPage /></Route></Router>);
-    
+
     expect(forms.getForm).toBeCalled();
 });
diff --git a/webpack.config.js b/webpack.config.js
index 1264ba1..93e7e9d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -65,12 +65,6 @@ module.exports = {
         }, {
             test: /\.svg$/,
             loader: '@svgr/webpack'
-        }, {
-            test: /\.css$/,
-            loader: 'raw-loader',
-            options: {
-                esModule: false
-            }
         }]
     },
     devServer: {
diff --git a/yarn.lock b/yarn.lock
index e1c7fde..2e4a587 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7343,14 +7343,6 @@ raw-body@2.4.0:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
-raw-loader@4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6"
-  integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==
-  dependencies:
-    loader-utils "^2.0.0"
-    schema-utils "^3.0.0"
-
 react-app-polyfill@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf"
-- 
cgit v1.2.3


From 2a3300109f41ea597bd299a53ce240a2183f0ae8 Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Sat, 16 Jan 2021 22:29:09 +0300
Subject: Minor Style Adjustments

Minor adjustments following the first round of open feedback.

Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 src/components/InputTypes/TextArea.tsx | 1 -
 src/components/Question.tsx            | 2 +-
 src/pages/FormPage.tsx                 | 4 ++++
 3 files changed, 5 insertions(+), 2 deletions(-)

(limited to 'src/components/Question.tsx')

diff --git a/src/components/InputTypes/TextArea.tsx b/src/components/InputTypes/TextArea.tsx
index 21300fb..6e46c27 100644
--- a/src/components/InputTypes/TextArea.tsx
+++ b/src/components/InputTypes/TextArea.tsx
@@ -9,7 +9,6 @@ interface TextAreaProps {
 
 const styles = css`
   min-height: 20rem;
-  min-width: 40%;
   width: 100%;
   box-sizing: border-box;
   resize: vertical;
diff --git a/src/components/Question.tsx b/src/components/Question.tsx
index 66c1668..54074f3 100644
--- a/src/components/Question.tsx
+++ b/src/components/Question.tsx
@@ -133,7 +133,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
             `;
 
             return <div css={styles}>
-                <h1 css={selectable}>{question.name}</h1>
+                <h1 css={[selectable, css`line-height: 2.5rem;`]}>{question.name}</h1>
                 { question.data["text"] ? <h3 css={selectable}>{question.data["text"]}</h3> : "" }
                 <hr css={css`color: gray; margin: 3rem 0;`}/>
             </div>;
diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx
index 647003f..2d34b3c 100644
--- a/src/pages/FormPage.tsx
+++ b/src/pages/FormPage.tsx
@@ -146,6 +146,10 @@ const closedHeaderStyles = css`
   font-size: 1.5rem;
 
   background-color: ${colors.error};
+  
+  @media (max-width: 500px) {
+    padding: 1rem 1.5rem;
+  }
 `;
 
 function FormPage(): JSX.Element {
-- 
cgit v1.2.3


From d7c20ba6e8f0675ad2fcd4fb41419de005ae3979 Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Sat, 16 Jan 2021 22:31:50 +0300
Subject: Rewrites Components For Accessibility

Makes major changes to the structuring of checkboxes, and ranges to be
more accessible to all users.

Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 src/commonStyles.tsx                   | 23 +++++++++++-
 src/components/InputTypes/Checkbox.tsx | 27 +++++---------
 src/components/InputTypes/Range.tsx    | 64 ++++++++++++----------------------
 src/components/InputTypes/index.tsx    |  2 +-
 src/components/Question.tsx            | 11 +++---
 5 files changed, 57 insertions(+), 70 deletions(-)

(limited to 'src/components/Question.tsx')

diff --git a/src/commonStyles.tsx b/src/commonStyles.tsx
index 5053846..71bbd13 100644
--- a/src/commonStyles.tsx
+++ b/src/commonStyles.tsx
@@ -14,6 +14,24 @@ const unselectable = css`
   user-select: none;
 `;
 
+const hiddenInput = css`
+  position: absolute;
+  opacity: 0;
+  height: 0;
+  width: 0;
+`;
+
+const multiSelectInput = css`
+  display: inline-block;
+  position: relative;
+
+  margin: 1rem 0.5rem 0 0;
+  border: whitesmoke 0.2rem solid;
+
+  background-color: whitesmoke;
+  transition: background-color 300ms;
+`;
+
 const textInputs = css`
   display: inline-block;
   width: min(20rem, 90%);
@@ -32,8 +50,11 @@ const textInputs = css`
   border-radius: 8px;
 `;
 
+
 export {
     selectable,
     unselectable,
-    textInputs
+    hiddenInput,
+    multiSelectInput,
+    textInputs,
 };
diff --git a/src/components/InputTypes/Checkbox.tsx b/src/components/InputTypes/Checkbox.tsx
index f63da31..3093caf 100644
--- a/src/components/InputTypes/Checkbox.tsx
+++ b/src/components/InputTypes/Checkbox.tsx
@@ -2,6 +2,7 @@
 import { jsx, css } from "@emotion/react";
 import React, { ChangeEvent } from "react";
 import colors from "../../colors";
+import { multiSelectInput, hiddenInput } from "../../commonStyles";
 
 interface CheckboxProps {
     index: number,
@@ -10,36 +11,25 @@ interface CheckboxProps {
 }
 
 const generalStyles = css`
+  cursor: pointer;
+        
   label {
-    display: inline-block;
-    position: relative;
-    top: 0.3rem;
-
     width: 1em;
     height: 1em;
+    top: 0.3rem;
 
-    margin: 1rem 0.5rem 0 0;
-    border: whitesmoke 0.2rem solid;
     border-radius: 25%;
-    
-    transition: background-color 300ms;
+    cursor: pointer;
   }
 
   .unselected {
     background-color: white;
   }
 
-  .unselected:hover {
+  .unselected:focus-within, :hover .unselected {
     background-color: lightgray;
   }
 
-  input {
-    position: absolute;
-    opacity: 0;
-    height: 0;
-    width: 0;
-  }
-
   .checkmark {
     position: absolute;
   }
@@ -65,9 +55,8 @@ const activeStyles = css`
 export default function Checkbox(props: CheckboxProps): JSX.Element {
     return (
         <label css={[generalStyles, activeStyles]}>
-            <label className="unselected">
-                <input type="checkbox" value={props.option}
-                    name={`${("000" + props.index).slice(-4)}. ${props.option}`} onChange={props.handler}/>
+            <label className="unselected" css={multiSelectInput}>
+                <input type="checkbox" value={props.option} css={hiddenInput} name={`${("000" + props.index).slice(-4)}. ${props.option}`} onChange={props.handler}/>
                 <span className="checkmark"/>
             </label>
             {props.option}<br/>
diff --git a/src/components/InputTypes/Range.tsx b/src/components/InputTypes/Range.tsx
index af46b05..e2f89f4 100644
--- a/src/components/InputTypes/Range.tsx
+++ b/src/components/InputTypes/Range.tsx
@@ -1,32 +1,13 @@
 /** @jsx jsx */
 import { jsx, css } from "@emotion/react";
-import React from "react";
+import React, { ChangeEvent } from "react";
 import colors from "../../colors";
+import { hiddenInput, multiSelectInput } from "../../commonStyles";
 
 interface RangeProps {
     question_id: string,
     options: Array<string>,
-    state_dict: Map<string, string | boolean | null>
-}
-
-interface handler_props {
-    state_dict: Map<string, string | boolean | null>,
-    ref: React.RefObject<HTMLLabelElement>
-}
-
-let last_selection: Element;
-function handler(this: handler_props): void {
-    if (last_selection) {
-        last_selection.classList.toggle("selected");
-    }
-
-    const dot: Element = this.ref.current!.lastElementChild!; // eslint-disable-line
-    dot.classList.toggle("selected");
-
-    last_selection = dot;
-
-    const value: string = this.ref.current!.textContent!; //eslint-disable-line
-    this.state_dict.set("value", value);
+    handler: (event: ChangeEvent<HTMLInputElement>) => void
 }
 
 const containerStyles = css`
@@ -57,26 +38,25 @@ const optionStyles = css`
   transition: transform 300ms;
 `;
 
-const rangeDotStyles = css`
-  .range_dot {
-    width: 0.8rem;
-    height: 0.8rem;
+const selectorStyles = css`
+  cursor: pointer;
+
+  div {
+    width: 1rem;
+    height: 1rem;
+
     background-color: whitesmoke;
 
-    border: 0.2rem solid whitesmoke;
     border-radius: 50%;
-
-    transition: background-color 300ms;
+    margin: 0 100% 0 0;
   }
 
-  .range_dot.selected {
-    background-color: ${colors.blurple};
+  :hover div, :focus-within div {
+    background-color: lightgray;
   }
 
-  @media (max-width: 800px) {
-    .range_dot {
-      margin-bottom: 1.5rem;
-    }
+  input:checked+div {
+    background-color: ${colors.blurple};
   }
 `;
 
@@ -88,15 +68,15 @@ const sliderContainerStyles = css`
   position: absolute;
   z-index: -1;
 
-  top: 2rem;
+  top: 2.1rem;
 
   transition: all 300ms;
   
   @media (max-width: 800px) {
     width: 0.5rem;
-    height: 88%;
+    height: 80%;
 
-    left: 0.32rem;
+    left: 0.4rem;
 
     background: whitesmoke;
   }
@@ -116,17 +96,17 @@ const sliderStyles = css`
 
 export default function Range(props: RangeProps): JSX.Element {
     const range = props.options.map((option, index) => {
-        const ref: React.RefObject<HTMLLabelElement> = React.createRef();
         return (
-            <label key={index} ref={ref} css={css`width: 1rem;`} onClick={handler.bind({state_dict: props.state_dict, ref})}>
+            <label css={[selectorStyles, css`width: 1rem`]} key={index}>
                 <span css={optionStyles}>{option}</span>
-                <div className="range_dot"/>
+                <input type="radio" name={props.question_id} css={hiddenInput} onChange={props.handler}/>
+                <div css={multiSelectInput}/>
             </label>
         );
     });
 
     return (
-        <div css={[containerStyles, rangeDotStyles]}>
+        <div css={containerStyles}>
             { range }
 
             <div css={sliderContainerStyles}>
diff --git a/src/components/InputTypes/index.tsx b/src/components/InputTypes/index.tsx
index b1d8afe..d75fbdc 100644
--- a/src/components/InputTypes/index.tsx
+++ b/src/components/InputTypes/index.tsx
@@ -54,7 +54,7 @@ export default function create_input({ question, public_state }: QuestionProp, h
         break;
 
     case QuestionType.Range:
-        result = <Range question_id={question.id} options={options} state_dict={public_state}/>;
+        result = <Range question_id={question.id} options={options} handler={handler}/>;
         break;
 
     case QuestionType.Code:
diff --git a/src/components/Question.tsx b/src/components/Question.tsx
index 54074f3..e1472e4 100644
--- a/src/components/Question.tsx
+++ b/src/components/Question.tsx
@@ -46,12 +46,13 @@ class RenderedQuestion extends React.Component<QuestionProp> {
         let value: string | boolean;
 
         switch (event.target.type) {
-        case QuestionType.Checkbox:
-            target = this.props.question.id;
+        case "checkbox":
+            target = event.target.name;
             value = event.target.checked;
             break;
 
-        case QuestionType.Radio:
+        case "radio":
+            // This handles radios and ranges, as they are both based on the same fundamental input type
             target = "value";
             if (event.target.parentElement) {
                 value = event.target.parentElement.innerText.trimEnd();
@@ -60,10 +61,6 @@ class RenderedQuestion extends React.Component<QuestionProp> {
             }
             break;
 
-        case QuestionType.Select:
-            // Handled by component
-            return;
-
         default:
             target = "value";
             value = event.target.value;
-- 
cgit v1.2.3


From 1f02ab5c692801709221d4e27f3001a40cb4e8ae Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Sun, 17 Jan 2021 02:41:24 +0300
Subject: Updates Switch Statement Indent

Changes the switch statement indentation level to 1 in the eslint
config, and updates files accordingly.

Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 .eslintrc.json                      |  5 ++-
 src/components/InputTypes/index.tsx | 48 ++++++++++++++--------------
 src/components/Question.tsx         | 64 ++++++++++++++++++-------------------
 src/pages/FormPage.tsx              |  4 +--
 4 files changed, 62 insertions(+), 59 deletions(-)

(limited to 'src/components/Question.tsx')

diff --git a/.eslintrc.json b/.eslintrc.json
index 5c7c77a..7785702 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -23,7 +23,10 @@
     "rules": {
         "indent": [
             "error",
-            4
+            4,
+            {
+                "SwitchCase": 1
+            }
         ],
         "linebreak-style": [
             "error",
diff --git a/src/components/InputTypes/index.tsx b/src/components/InputTypes/index.tsx
index d75fbdc..5154f9c 100644
--- a/src/components/InputTypes/index.tsx
+++ b/src/components/InputTypes/index.tsx
@@ -33,37 +33,37 @@ export default function create_input({ question, public_state }: QuestionProp, h
 
     /* eslint-disable react/react-in-jsx-scope */
     switch (question.type) {
-    case QuestionType.TextArea:
-        result = <TextArea handler={handler}/>;
-        break;
+        case QuestionType.TextArea:
+            result = <TextArea handler={handler}/>;
+            break;
 
-    case QuestionType.Checkbox:
-        result = options.map((option, index) => <Checkbox index={index} option={option} handler={handler} key={index}/>);
-        break;
+        case QuestionType.Checkbox:
+            result = options.map((option, index) => <Checkbox index={index} option={option} handler={handler} key={index}/>);
+            break;
 
-    case QuestionType.Radio:
-        result = options.map((option, index) => <Radio option={option} question_id={question.id} handler={handler} key={index}/>);
-        break;
+        case QuestionType.Radio:
+            result = options.map((option, index) => <Radio option={option} question_id={question.id} handler={handler} key={index}/>);
+            break;
 
-    case QuestionType.Select:
-        result = <Select options={options} state_dict={public_state}/>;
-        break;
+        case QuestionType.Select:
+            result = <Select options={options} state_dict={public_state}/>;
+            break;
 
-    case QuestionType.ShortText:
-        result = <ShortText handler={handler}/>;
-        break;
+        case QuestionType.ShortText:
+            result = <ShortText handler={handler}/>;
+            break;
 
-    case QuestionType.Range:
-        result = <Range question_id={question.id} options={options} handler={handler}/>;
-        break;
+        case QuestionType.Range:
+            result = <Range question_id={question.id} options={options} handler={handler}/>;
+            break;
 
-    case QuestionType.Code:
-        // TODO: Implement
-        result = <Code handler={handler}/>;
-        break;
+        case QuestionType.Code:
+            // TODO: Implement
+            result = <Code handler={handler}/>;
+            break;
 
-    default:
-        result = <TextArea handler={handler}/>;
+        default:
+            result = <TextArea handler={handler}/>;
     }
     /* eslint-enable react/react-in-jsx-scope */
 
diff --git a/src/components/Question.tsx b/src/components/Question.tsx
index e1472e4..34d12f7 100644
--- a/src/components/Question.tsx
+++ b/src/components/Question.tsx
@@ -46,24 +46,24 @@ class RenderedQuestion extends React.Component<QuestionProp> {
         let value: string | boolean;
 
         switch (event.target.type) {
-        case "checkbox":
-            target = event.target.name;
-            value = event.target.checked;
-            break;
-
-        case "radio":
-            // This handles radios and ranges, as they are both based on the same fundamental input type
-            target = "value";
-            if (event.target.parentElement) {
-                value = event.target.parentElement.innerText.trimEnd();
-            } else {
-                value = event.target.value;
-            }
-            break;
+            case "checkbox":
+                target = event.target.name;
+                value = event.target.checked;
+                break;
+
+            case "radio":
+                // This handles radios and ranges, as they are both based on the same fundamental input type
+                target = "value";
+                if (event.target.parentElement) {
+                    value = event.target.parentElement.innerText.trimEnd();
+                } else {
+                    value = event.target.value;
+                }
+                break;
 
-        default:
-            target = "value";
-            value = event.target.value;
+            default:
+                target = "value";
+                value = event.target.value;
         }
 
         this._setState(target, value);
@@ -85,21 +85,21 @@ class RenderedQuestion extends React.Component<QuestionProp> {
 
         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;
+                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;
             }
         }
     }
diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx
index 2d34b3c..c49b9fd 100644
--- a/src/pages/FormPage.tsx
+++ b/src/pages/FormPage.tsx
@@ -177,8 +177,8 @@ function FormPage(): JSX.Element {
 
             // TODO: Parse input from each question, and submit
             switch (question.type) {
-            default:
-                console.log(question.id, prop.props.public_state);
+                default:
+                    console.log(question.id, prop.props.public_state);
             }
         });
 
-- 
cgit v1.2.3


From a084f79568f2b4808789bb65034e677a4ffdd520 Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Sun, 17 Jan 2021 02:47:07 +0300
Subject: Removes Leading Underscore From Variables

Co-authored-by: Joe Banks <joseph@josephbanks.me>
Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 src/components/InputTypes/index.tsx |  4 ++--
 src/components/Question.tsx         | 18 +++++++++---------
 2 files changed, 11 insertions(+), 11 deletions(-)

(limited to 'src/components/Question.tsx')

diff --git a/src/components/InputTypes/index.tsx b/src/components/InputTypes/index.tsx
index 5154f9c..f1e0b30 100644
--- a/src/components/InputTypes/index.tsx
+++ b/src/components/InputTypes/index.tsx
@@ -11,7 +11,7 @@ import React, { ChangeEvent } from "react";
 import { QuestionType } from "../../api/question";
 import { QuestionProp } from "../Question";
 
-const _require_options: Array<QuestionType> = [
+const require_options: Array<QuestionType> = [
     QuestionType.Radio,
     QuestionType.Checkbox,
     QuestionType.Select,
@@ -26,7 +26,7 @@ export default function create_input({ question, public_state }: QuestionProp, h
     let options: string[] = question.data["options"];
 
     // Catch input types that require options but don't have any
-    if ((options === undefined || typeof options !== "object") && _require_options.includes(question.type)) {
+    if ((options === undefined || typeof options !== "object") && require_options.includes(question.type)) {
         // TODO: Implement some sort of warning here
         options = [];
     }
diff --git a/src/components/Question.tsx b/src/components/Question.tsx
index 34d12f7..735d69b 100644
--- a/src/components/Question.tsx
+++ b/src/components/Question.tsx
@@ -6,7 +6,7 @@ import { Question, QuestionType } from "../api/question";
 import { selectable } from "../commonStyles";
 import create_input from "./InputTypes";
 
-const _skip_normal_state: Array<QuestionType> = [
+const skip_normal_state: Array<QuestionType> = [
     QuestionType.Radio,
     QuestionType.Checkbox,
     QuestionType.Select,
@@ -28,12 +28,12 @@ class RenderedQuestion extends React.Component<QuestionProp> {
             this.handler = this.normal_handler.bind(this);
         }
 
-        if (!_skip_normal_state.includes(props.question.type)) {
-            this._setState("value", "");
+        if (!skip_normal_state.includes(props.question.type)) {
+            this.setPublicState("value", "");
         }
     }
 
-    _setState(target: string, value: string | boolean | null, callback?:() => void): void {
+    setPublicState(target: string, value: string | boolean | null, callback?:() => void): void {
         this.setState({[target]: value}, callback);
         this.props.public_state.set(target, value);
     }
@@ -52,7 +52,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
                 break;
 
             case "radio":
-                // This handles radios and ranges, as they are both based on the same fundamental input type
+            // This handles radios and ranges, as they are both based on the same fundamental input type
                 target = "value";
                 if (event.target.parentElement) {
                     value = event.target.parentElement.innerText.trimEnd();
@@ -66,7 +66,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
                 value = event.target.value;
         }
 
-        this._setState(target, value);
+        this.setPublicState(target, value);
 
         // Toggle checkbox class
         if (event.target.type == "checkbox" && event.target.parentElement !== null) {
@@ -76,7 +76,7 @@ class RenderedQuestion extends React.Component<QuestionProp> {
     }
 
     text_area_handler(event: ChangeEvent<HTMLTextAreaElement>): void {
-        this._setState("value", event.target.value);
+        this.setPublicState("value", event.target.value);
     }
 
     componentDidMount(): void {
@@ -91,14 +91,14 @@ class RenderedQuestion extends React.Component<QuestionProp> {
                     }
 
                     options.forEach((option, index) => {
-                        this._setState(`${("000" + index).slice(-4)}. ${option}`, false);
+                        this.setPublicState(`${("000" + index).slice(-4)}. ${option}`, false);
                     });
                     break;
 
                 case QuestionType.Range:
                 case QuestionType.Radio:
                 case QuestionType.Select:
-                    this._setState("value", null);
+                    this.setPublicState("value", null);
                     break;
             }
         }
-- 
cgit v1.2.3

