aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorGravatar Hassan Abouelela <[email protected]>2021-01-06 08:00:31 +0300
committerGravatar Hassan Abouelela <[email protected]>2021-01-06 09:36:01 +0300
commit996c14afb9d81e962ef66b99bd869bce4f3688f0 (patch)
treeea19a979c62a0da3685a1ea44a624ee2863320cc /src/components
parentFixes Model Casing (diff)
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 <[email protected]>
Diffstat (limited to 'src/components')
-rw-r--r--src/components/InputTypes/Checkbox.tsx62
-rw-r--r--src/components/InputTypes/Radio.tsx4
-rw-r--r--src/components/InputTypes/Range.tsx101
-rw-r--r--src/components/InputTypes/Select.tsx109
-rw-r--r--src/components/InputTypes/ShortText.tsx3
-rw-r--r--src/components/InputTypes/TextArea.tsx14
-rw-r--r--src/components/Question.tsx61
-rw-r--r--src/components/ScrollToTop.tsx3
8 files changed, 315 insertions, 42 deletions
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;