From c8046900fd94baa5264e15a527c24346be024b73 Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Mon, 4 Jan 2021 02:33:35 +0300
Subject: Implements Scroll Button

Adds a scroll to top button to the landing page, and form pages to make
navigation easier on long pages.

Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 src/components/ScrollToTop.tsx | 87 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 src/components/ScrollToTop.tsx

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

diff --git a/src/components/ScrollToTop.tsx b/src/components/ScrollToTop.tsx
new file mode 100644
index 0000000..6af938d
--- /dev/null
+++ b/src/components/ScrollToTop.tsx
@@ -0,0 +1,87 @@
+/** @jsx jsx */
+import { jsx, css } from "@emotion/react";
+import React from "react";
+
+const styles = css`
+  width: 2.5rem;
+  height: 2.5rem;
+
+  position: fixed;
+  bottom: 3rem;
+  right: 3rem;
+  
+  background-color: #7289DA; /* Blurple */
+  border-radius: 50%;
+
+  opacity: 0;
+  transition: opacity 300ms;
+  
+  :after {
+    display: inline-block;
+    content: "";
+
+    position: fixed;
+    bottom: 3.5rem;
+    right: 3.65rem;
+    
+    border: solid whitesmoke;
+    border-width: 0.35rem 0.35rem 0 0;
+    padding: 0.4rem;
+
+    transform: rotate(-45deg);
+  }
+  
+  @media (max-width: 800px) {
+    bottom: 1.5rem;
+    right: 1.5rem;
+    
+    :after {
+      bottom: 2rem;
+      right: 2.15rem;
+    }
+  }
+`;
+
+let last_ref: React.RefObject<HTMLDivElement>;
+
+class ScrollToTop extends React.Component {
+    constructor(props: Record<string, never>) {
+        super(props);
+        last_ref = React.createRef();
+    }
+
+    handleScroll(): void {
+        if (!last_ref.current) return;
+
+        if (window.pageYOffset > 250) {
+            last_ref.current.style.opacity = "1";
+        } else {
+            last_ref.current.style.opacity = "0";
+        }
+    }
+
+    componentDidMount(): void {
+        // Register event handler
+        window.addEventListener("scroll", this.handleScroll, {passive: true});
+    }
+
+    componentDidUpdate(): void {
+        // Hide previous iterations, and register handler for current one
+        if (last_ref.current) {
+            last_ref.current.style.opacity = "0";
+        }
+
+        window.addEventListener("scroll", this.handleScroll, {passive: true});
+    }
+
+    componentWillUnmount(): void {
+        // Unregister handler
+        window.removeEventListener("scroll", this.handleScroll);
+    }
+
+    render(): JSX.Element {
+        return <div css={styles} key={Date.now()} ref={last_ref} onClick={() => window.scrollTo({top: 0, behavior: "smooth"})}/>;
+    }
+}
+
+export default ScrollToTop;
-- 
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/ScrollToTop.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 74e1cf9f3be47b85a8a29c46786b4a839f977eee Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Sat, 16 Jan 2021 22:28:02 +0300
Subject: Polyfills Smooth Scroll

Pollyfills smooth scroll on the scroll to top button.

Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 package.json                   |  2 ++
 src/components/ScrollToTop.tsx |  3 +++
 yarn.lock                      | 10 ++++++++++
 3 files changed, 15 insertions(+)

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

diff --git a/package.json b/package.json
index c3e21ce..916f69e 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
     "@types/react-dom": "17.0.0",
     "@types/react-router-dom": "5.1.7",
     "@types/react-transition-group": "4.4.0",
+    "@types/smoothscroll-polyfill": "0.3.1",
     "axios": "0.21.1",
     "copy-webpack-plugin": "7.0.0",
     "cross-env": "7.0.3",
@@ -28,6 +29,7 @@
     "react-router-dom": "5.2.0",
     "react-spinners": "0.10.4",
     "react-transition-group": "4.4.1",
+    "smoothscroll-polyfill": "0.4.4",
     "swc-loader": "0.1.12",
     "typescript": "4.1.3",
     "webpack": "5.11.1",
diff --git a/src/components/ScrollToTop.tsx b/src/components/ScrollToTop.tsx
index 4888dec..2c5e9ca 100644
--- a/src/components/ScrollToTop.tsx
+++ b/src/components/ScrollToTop.tsx
@@ -3,6 +3,9 @@ import { jsx, css } from "@emotion/react";
 import React from "react";
 import colors from "../colors";
 
+import smoothscroll from "smoothscroll-polyfill";
+smoothscroll.polyfill();
+
 const styles = css`
   width: 2.5rem;
   height: 2.5rem;
diff --git a/yarn.lock b/yarn.lock
index 2e4a587..25da0e8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2201,6 +2201,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/smoothscroll-polyfill@0.3.1":
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/@types/smoothscroll-polyfill/-/smoothscroll-polyfill-0.3.1.tgz#77fb3a6e116bdab4a5959122e3b8e201224dcd49"
+  integrity sha512-+KkHw4y+EyeCtVXET7woHUhIbfWFCflc0E0mZnSV+ZdjPQeHt/9KPEuT7gSW/kFQ8O3EG30PLO++YhChDt8+Ag==
+
 "@types/source-list-map@*":
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@@ -8063,6 +8068,11 @@ slice-ansi@^4.0.0:
     astral-regex "^2.0.0"
     is-fullwidth-code-point "^3.0.0"
 
+smoothscroll-polyfill@0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz#3a259131dc6930e6ca80003e1cb03b603b69abf8"
+  integrity sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==
+
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
-- 
cgit v1.2.3


From 0b13917c6200e9d743582bd2aa185f5fef573977 Mon Sep 17 00:00:00 2001
From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
Date: Sun, 17 Jan 2021 02:28:20 +0300
Subject: Shows Pointer On Scroll Button

Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com>
---
 src/components/ScrollToTop.tsx | 2 ++
 1 file changed, 2 insertions(+)

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

diff --git a/src/components/ScrollToTop.tsx b/src/components/ScrollToTop.tsx
index 2c5e9ca..231c9e8 100644
--- a/src/components/ScrollToTop.tsx
+++ b/src/components/ScrollToTop.tsx
@@ -59,8 +59,10 @@ class ScrollToTop extends React.Component {
 
         if (window.pageYOffset > 250) {
             last_ref.current.style.opacity = "1";
+            last_ref.current.style.cursor = "pointer";
         } else {
             last_ref.current.style.opacity = "0";
+            last_ref.current.style.cursor = "default";
         }
     }
 
-- 
cgit v1.2.3

