diff options
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | src/colors.ts | 2 | ||||
| -rw-r--r-- | src/commonStyles.tsx | 39 | ||||
| -rw-r--r-- | src/components/InputTypes/Checkbox.tsx | 62 | ||||
| -rw-r--r-- | src/components/InputTypes/Radio.tsx | 4 | ||||
| -rw-r--r-- | src/components/InputTypes/Range.tsx | 101 | ||||
| -rw-r--r-- | src/components/InputTypes/Select.tsx | 109 | ||||
| -rw-r--r-- | src/components/InputTypes/ShortText.tsx | 3 | ||||
| -rw-r--r-- | src/components/InputTypes/TextArea.tsx | 14 | ||||
| -rw-r--r-- | src/components/Question.tsx | 61 | ||||
| -rw-r--r-- | src/components/ScrollToTop.tsx | 3 | ||||
| -rw-r--r-- | src/pages/FormPage.tsx | 168 | ||||
| -rw-r--r-- | src/pages/css/FormPage.css | 452 | ||||
| -rw-r--r-- | src/tests/pages/FormPage.test.tsx | 2 | ||||
| -rw-r--r-- | webpack.config.js | 6 | ||||
| -rw-r--r-- | yarn.lock | 8 | 
16 files changed, 498 insertions, 537 deletions
| 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: { @@ -7343,14 +7343,6 @@ [email protected]:      iconv-lite "0.4.24"      unpipe "1.0.0" -  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" -    version "2.0.0"    resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" | 
