diff options
| author | 2021-01-16 22:33:18 +0300 | |
|---|---|---|
| committer | 2021-01-16 22:35:22 +0300 | |
| commit | e2eff909dd13d27d06826292f832f4e2c21ced76 (patch) | |
| tree | 38f69e548b70f48aeae912cf49ef7afd56eaf4bc /src/components | |
| parent | Rewrites Components For Accessibility (diff) | |
Rewrites Select Component
Rewrites select to be more accessible, and to have more predictable
behavior across browsers and devices.
Signed-off-by: Hassan Abouelela <[email protected]>
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/InputTypes/Select.tsx | 154 | 
1 files changed, 98 insertions, 56 deletions
| diff --git a/src/components/InputTypes/Select.tsx b/src/components/InputTypes/Select.tsx index de763bf..74c154b 100644 --- a/src/components/InputTypes/Select.tsx +++ b/src/components/InputTypes/Select.tsx @@ -1,43 +1,60 @@  /** @jsx jsx */  import { jsx, css } from "@emotion/react";  import React from "react"; +import { hiddenInput } from "../../commonStyles";  interface SelectProps {      options: Array<string>,      state_dict: Map<string, string | boolean | null>  } -interface HandlerProps { -    props: SelectProps, -    ref: React.RefObject<HTMLDivElement> -} -  const containerStyles = css` -  .container { -    display: inline-block; -    position: relative; +  position: relative; +  width: min(20rem, 90%); -    width: min(20rem, 90%); -    height: 100%; -    min-height: 2rem; +  color: black; +  cursor: pointer; -    background: whitesmoke; +  :focus-within .selected_container { +    border-bottom-left-radius: 0; +    border-bottom-right-radius: 0; -    color: black; -    text-align: center; +    border-bottom-color: transparent; +  } +`; -    margin-bottom: 0; +const mainWindowStyles = css` +  display: inline-block; +  position: relative; +  background: whitesmoke; -    border: 0.1rem solid black; -    border-radius: 8px; +  width: 100%; +  height: 100%; +  min-height: 2.5rem; + +  margin-bottom: 0; -    transition: border-radius 400ms; +  overflow: hidden; +  z-index: 1; + +  :hover, :focus-within { +    background-color: lightgray;    } -  .container.active { -    height: auto; -    border-radius: 8px 8px 0 0; +  .selected_option { +    position: absolute; +    height: 100%; +    width: 100%; + +    outline: none; +    padding-left: 0.75rem; +    line-height: 250%;    } + +  border: 0.1rem solid black; +  border-radius: 8px; + +  transition: border-radius 400ms;  `;  const arrowStyles = css` @@ -58,83 +75,108 @@ const arrowStyles = css`      transition: transform 400ms;    } -  .active .arrow { +  :focus-within .arrow {      transform: translateY(40%) rotate(225deg);    }  `; -const optionContainer = css` +const optionContainerStyles = css`    .option_container { -    display: block;      position: absolute;      width: 100%; +    height: 0; -    /* Need to account for margin */ -    left: -0.1rem; +    top: 2.3rem; +    padding-top: 0.2rem;      visibility: hidden;      opacity: 0; -    background: whitesmoke;      overflow: hidden; +    background: whitesmoke;      border: 0.1rem solid black;      border-radius: 0 0 8px 8px;      border-top: none;      transition: opacity 400ms, visibility 400ms; + +    * { +      cursor: pointer; +    }    } -  .active .option_container { +  :focus-within .option_container { +    height: auto;      visibility: visible;      opacity: 1;    } + +  .option_container .hidden { +    display: none; +  }  `; -class Select extends React.Component<SelectProps> { -    click_handler(this: HandlerProps, event: React.MouseEvent<HTMLDivElement, MouseEvent>): void { -        if (!this.ref.current) { -            return; -        } +const inputStyles = css` +  position: absolute; +  width: 100%; +  height: 100%; + +  z-index: 2; + +  margin: 0; +  border: none; +  outline: none; +`; -        this.ref.current.classList.toggle("active"); +const optionStyles = css` +  position: relative; -        const target: Element = (event.target as Element); -        if (target.id === "option") { -            const selected_option: Element = this.ref.current.getElementsByClassName("selected_option")[0]; -            const new_option_text: string = target.textContent ?? "..."; +  :hover, :focus-within { +    background-color: lightgray; +  } -            if (selected_option.textContent === "..." && target.parentElement) { -                target.parentElement.remove(); -            } else { -                target.textContent = selected_option.textContent; -            } +  div { +    padding: 0.75rem; +  } +`; -            selected_option.textContent = new_option_text; -            this.props.state_dict.set("value", selected_option.textContent); +class Select extends React.Component<SelectProps> { +    handler(selected_option: React.RefObject<HTMLDivElement>, event: React.ChangeEvent<HTMLInputElement>): void { +        const option_container = event.target.parentElement; +        if (!option_container || !option_container.parentElement || !selected_option.current) { +            return;          } + +        // Update stored value +        this.props.state_dict.set("value", option_container.textContent); + +        // Close the menu +        selected_option.current.focus(); +        selected_option.current.blur(); +        selected_option.current.textContent = option_container.textContent;      }      render(): JSX.Element { -        const container_ref: React.RefObject<HTMLDivElement> = React.createRef(); +        const selected_option_ref: React.RefObject<HTMLDivElement> = React.createRef(); -        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> +        return ( +            <div css={[containerStyles, arrowStyles, optionContainerStyles]}> +                <div className="selected_container" css={mainWindowStyles}> +                    <span className="arrow"/> +                    <div tabIndex={0} className="selected_option" ref={selected_option_ref}>...</div> +                </div> -                <div className="option_container"> +                <div className="option_container" tabIndex={-1} css={css`outline: none;`}>                      { 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 key={index} css={optionStyles}> +                            <input type="checkbox" css={[hiddenInput, inputStyles]} onChange={event => this.handler.call(this, selected_option_ref, event)}/> +                            <div>{option}</div>                          </div>                      )) }                  </div>              </div>          ); - -        return <div css={[containerStyles, arrowStyles, optionContainer]}>{ element }</div>;      }  } | 
