All files / components/inputs/Select index.ts

0% Statements 0/31
0% Branches 0/9
0% Functions 0/7
0% Lines 0/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                                                                                                                                                                                                                                                                 
/* eslint-disable max-lines-per-function */
 
import React, {
  FocusEventHandler,
  ForwardedRef,
  RefObject,
  useMemo,
  useRef
} from "react"
import { prepareComponent, useToggle } from "@bluframe/grapple"
import Select from "./Select"
 
export type OptionValue = number | string
 
export interface Option {
  label: React.ReactNode
  value: OptionValue
}
 
export interface Props {
  defaultValue?: string
  className?: string
  label: React.ReactNode
  name: string
  onBlur?: FocusEventHandler
  // eslint-disable-next-line no-unused-vars
  onChange: (value: OptionValue | null) => void
  options: Option[]
  placeholder?: string
  ref?: ForwardedRef<HTMLInputElement>
  value?: OptionValue | null
}
 
export interface ComponentProps extends Props {
  displayValue: React.ReactNode
  isControlled: boolean
  isLabelShrunk: boolean
  isOpen: boolean
  // eslint-disable-next-line no-unused-vars
  onChange: (value: OptionValue | null) => () => void
  onToggleIsOpen: () => void
  wrapperRef: RefObject<HTMLDivElement>
}
 
const DEFAULT_IS_OPEN = false
 
const usePrepareComponent = (props: Props): ComponentProps => {
  const [isOpen, toggleIsOpen] = useToggle(DEFAULT_IS_OPEN)
 
  // Determine if the component is controlled
  const isControlled = props.value !== undefined
 
  const wrapperRef = useRef<HTMLDivElement>(null)
 
  // Get displayValue and isLabelShrunk
  const [displayValue, isLabelShrunk] = useMemo(() => {
    // Get selected option
    const selectedOption = props.options.find(
      (option) =>
        (!isControlled && option.value === props.defaultValue) ||
        option.value === props.value
    )
 
    // If selected option exists, return its label and shrink the label
    Iif (selectedOption) {
      return [selectedOption.label, true]
    }
 
    // If no selected option and placeholder exists, return placeholder and shrink the label
    Iif (props.placeholder) {
      return [props.placeholder, true]
    }
 
    // Otherwise, return the label and shrink it when options are open
    return ["", isOpen]
  }, [
    props.defaultValue,
    props.options,
    props.value,
    props.placeholder,
    isControlled,
    isOpen
  ])
 
  const onBlur: FocusEventHandler = (event) => {
    Iif (props.onBlur) {
      props.onBlur(event)
    }
  }
 
  const onToggleIsOpen = () => {
    // If the dropdown is currently open and is being closed by a click, trigger onBlur
    Iif (isOpen) {
      Iif (wrapperRef.current) {
        wrapperRef.current.blur()
      }
    }
 
    // Toggle options dropdown
    toggleIsOpen()
  }
 
  const onChange = (value: OptionValue | null) => () => {
    // Select via onSelect from props
    props.onChange(value)
 
    // If open close options dropdown
    Iif (isOpen) {
      onToggleIsOpen()
    }
  }
 
  return {
    ...props,
    displayValue,
    isControlled,
    isLabelShrunk,
    isOpen,
    onBlur,
    onChange,
    onToggleIsOpen,
    wrapperRef
  }
}
 
export default prepareComponent<Props, ComponentProps>(usePrepareComponent)(
  Select
)