Bladeren bron

Dropdown component. First iteration.

Pedro Semeano 4 jaren geleden
bovenliggende
commit
e2ef52d796

+ 90 - 0
packages/components/src/components/Dropdown/Dropdown.style.ts

@@ -0,0 +1,90 @@
+import { css } from "@emotion/core"
+import { typography, colors, spacing } from "./../../theme"
+
+export type DropdownStyleProps = {
+  disabled?: boolean
+  focus?: boolean
+  error?: boolean
+  isActive?: boolean
+}
+
+export let makeStyles = ({
+  disabled = false,
+  focus = false,
+  error = false,
+  isActive = false,
+}: DropdownStyleProps) => {
+
+  const fieldWidth = "250px"
+
+  const borderColor = disabled ? colors.gray[200] :
+    error ? colors.error :
+    focus ? colors.blue[500] :
+    isActive ? colors.gray[200] : colors.gray[400]
+
+  return {
+    wrapper: css`
+      display: block;
+      max-width: ${fieldWidth};
+      font-family: ${typography.fonts.base};
+    `,
+    container: css`
+      position: relative;
+      width: 100%;
+      height: 50px;
+      display: inline-flex;
+      cursor: ${disabled ? "not-allowed" : "default"};
+    `,
+    border: css`
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      border: 1px solid ${borderColor};
+      display: flex;
+      align-items: center;
+      justify-content: left;
+    `,
+    label: css`
+      color: ${error ? colors.error : colors.gray[400]};
+      padding: 0 ${spacing.xxxxl} 0 ${spacing.s};
+      background-color: ${colors.black};
+    `,
+    input: css`
+      display: none;
+      width: 100%;
+      margin: 0 ${spacing.xxxxl} 0 ${spacing.s};
+      background: none;
+      border: none;
+      color: ${colors.white};
+      outline: none;
+    `,
+    icon: css`
+      color: ${colors.gray[300]};
+      font-size: ${typography.sizes.icon.medium};
+      position: absolute;
+      top: ${spacing.m};
+      right: ${spacing.s};
+    `,
+    helper: css`
+      color: ${error ? colors.error : colors.gray[400]};
+      font-size: ${typography.sizes.caption};
+      margin: ${spacing.xxs} ${spacing.xs};
+    `,
+    options: css`
+      background-color: ${colors.gray[700]};
+      color: ${colors.white};
+      display: block;
+      width: 100%;
+      position: absolute;
+      top: 50px;
+    `,
+    option: css`
+      padding: ${spacing.s};
+      &:hover {
+        background-color: ${colors.gray[600]}
+      }
+    `
+  }
+}

+ 91 - 0
packages/components/src/components/Dropdown/Dropdown.tsx

@@ -0,0 +1,91 @@
+import React, { useState, useRef, useEffect } from "react"
+import { makeStyles, DropdownStyleProps } from "./Dropdown.style"
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
+import { faChevronDown } from "@fortawesome/free-solid-svg-icons"
+import { spacing } from "./../../theme"
+
+type DropdownOption = {
+  text: string
+  value: string
+}
+
+type DropdownProps = {
+  label: string
+  helper?: string
+  value?: string
+  options: Array<DropdownOption>
+  onChange?: (option: DropdownOption) => void
+} & DropdownStyleProps
+
+export default function Dropdown({
+  label,
+  helper = "",
+  value = "",
+  options,
+  disabled = false,
+  onChange,
+  ...styleProps
+}: DropdownProps) {
+
+  const inputRef = useRef(null)
+  const [isActive, setIsActive] = useState(!!value)
+  const [inputTextValue, setInputTextValue] = useState(!!value ? options.find(option => option.value === value).text : "")
+  const [showOptions, setShowOptions] = useState(false)
+  const styles = makeStyles({ isActive, disabled, ...styleProps })
+
+  function onToggleDropdown(): void {
+    if (!disabled) {
+      setShowOptions(!showOptions)
+    }
+  }
+
+  function onOptionSelected(option: DropdownOption): void {
+    setIsActive(false)
+    setInputTextValue(option.text)
+    onChange(option)
+  } 
+  
+  return (
+    <div css={styles.wrapper}>
+      <div css={styles.container} onClick={onToggleDropdown}>
+        <div css={styles.border}>
+          <div
+            css={styles.label}
+            style={!inputTextValue && !isActive ? {} : {
+              position: "absolute",
+              top: "-8px",
+              left: "5px",
+              fontSize: "0.7rem",
+              padding: `0 ${spacing.xs}`
+            }}>
+              {label}
+          </div>
+          <input
+            css={styles.input}
+            style={{ display: !!inputTextValue || isActive ? "block" : "none"}}
+            ref={inputRef}
+            type="text"
+            disabled={true}
+            value={inputTextValue}
+          />
+          <FontAwesomeIcon icon={faChevronDown} css={styles.icon} />
+        </div>
+        {showOptions &&
+          <div css={styles.options}>
+            {options.map((option, index) =>
+              <div
+                key={`${label}-${index}`}
+                css={styles.option}
+                defaultValue={option.value}
+                onClick={() => onOptionSelected(option)}>
+                  {option.text}
+              </div>)}
+          </div>
+        }
+      </div>
+      {!!helper &&
+        <p css={styles.helper}>{helper}</p>
+      }
+    </div>
+  )
+}

+ 3 - 0
packages/components/src/components/Dropdown/index.ts

@@ -0,0 +1,3 @@
+import { memo } from "react"
+import Dropdown from "./Dropdown"
+export default memo(Dropdown)

+ 3 - 2
packages/components/src/index.ts

@@ -1,8 +1,9 @@
 export { default as Button } from "./components/Button"
+export { default as Dropdown } from "./components/Dropdown"
+export { default as Grid } from "./components/Grid"
+export { default as Header } from "./components/Header"
 export { default as NavButton } from "./components/NavButton"
 export { default as TagButton } from "./components/TagButton"
-export { default as Header } from "./components/Header"
 export { default as Tag } from "./components/Tag"
 export { default as TextField } from "./components/TextField"
 export { default as Typography } from "./components/Typography"
-export { default as Grid } from "./components/Grid"

+ 35 - 0
packages/components/stories/07-Dropdown.stories.tsx

@@ -0,0 +1,35 @@
+import React from "react"
+import { Dropdown } from "./../src"
+
+export default {
+  title: "Dropdown",
+  component: Dropdown,
+}
+
+const options = [
+  {
+    text: "Option 1",
+    value: "1"
+  },
+  {
+    text: "Option 2",
+    value: "2"
+  },
+  {
+    text: "Option 3",
+    value: "3"
+  }
+]
+
+export const Primary = () => (
+  <div style={{ backgroundColor: "black", padding: "50px 20px" }}>
+    <Dropdown label="Label" options={options} onChange={(option) => console.log(option)} />
+  </div>
+)
+
+export const PrimaryWithValue = () => (
+  <div style={{ backgroundColor: "black", padding: "50px 20px" }}>
+    <Dropdown label="Label" options={options} value={options[1].value} onChange={(option) => console.log(option)} />
+  </div>
+)
+