synapsecns/sanguine

View on GitHub
packages/widget/src/components/ui/ChainPopoverSelect.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { useState, useEffect } from 'react'
import _ from 'lodash'
import { Chain } from 'types'

import usePopover from '@/hooks/usePopoverRef'
import { DownArrow } from '@/components/icons/DownArrow'
import { SearchInput } from './SearchInput'
import { TabOption, ToggleTabs } from './ToggleTabs'
import { ChainOption } from './ChainOption'
import { useChainInputFilter } from '@/hooks/useChainInputFilter'

type PopoverSelectProps = {
  options: Chain[]
  remaining: Chain[]
  targets: Chain[]
  onSelect: (selected: Chain) => void
  selected: Chain
  label: string
  isOrigin: boolean
}

export const ChainPopoverSelect = ({
  options,
  remaining,
  targets,
  onSelect,
  selected,
  label,
  isOrigin,
}: PopoverSelectProps) => {
  const { popoverRef, isOpen, togglePopover, closePopover } = usePopover()
  const [activeTab, setActiveTab] = useState<TabOption>(
    isOrigin ? 'All' : 'Target'
  )

  useEffect(() => {
    if (!targets || _.isEmpty(targets)) {
      setActiveTab('All')
    } else if (isOrigin) {
      setActiveTab('All')
    } else if (!isOrigin) {
      setActiveTab('Target')
    }
  }, [targets, isOrigin])

  const handleSelect = (option: Chain) => {
    onSelect(option)
    closePopover()
  }

  const handleTabSelect = (tab: TabOption) => {
    setActiveTab(tab)
  }

  const {
    filterValue,
    setFilterValue,
    filteredOptions,
    filteredRemaining,
    filteredTargets,
    hasFilteredRemaining,
    hasFilteredResults,
    hasFilteredTargets,
  } = useChainInputFilter(options, remaining, targets, isOpen)

  return (
    <div
      data-test-id="chain-popover-select"
      className="relative w-min col-span-full"
      ref={popoverRef}
    >
      <div
        id={`${isOrigin ? 'origin' : 'destination'}-chain-select`}
        onClick={() => togglePopover()}
        style={{ background: 'var(--synapse-select-bg)' }}
        className={`
          flex px-2.5 py-1.5 gap-2 items-center rounded
          text-[--synapse-select-text] whitespace-nowrap
          border border-solid border-[--synapse-select-border]
          cursor-pointer hover:border-[--synapse-focus]
        `}
      >
        {selected?.imgUrl && (
          <img
            src={selected?.imgUrl}
            alt={`${selected?.name} chain icon`}
            className="inline w-4 h-4"
          />
        )}
        {selected?.name || 'Network'}
        <DownArrow />
      </div>
      {isOpen && (
        <div
          style={{ background: 'var(--synapse-select-bg)' }}
          className={`
            absolute z-50 mt-1 max-h-60 min-w-48 rounded
            shadow popover text-left list-none overflow-y-auto
            border border-solid border-[--synapse-select-border]
            animate-slide-down origin-top
          `}
        >
          <div className="grid gap-1 p-1">
            <SearchInput
              inputValue={filterValue}
              setInputValue={setFilterValue}
              placeholder="Search Chains"
              isActive={isOpen}
            />
            {targets && targets.length > 0 && (
              <ToggleTabs
                selectedTab={activeTab}
                onTabSelect={handleTabSelect}
              />
            )}
          </div>
          {activeTab === 'All' ? (
            hasFilteredResults ? (
              <ul className="p-0 m-0">
                {filteredOptions.map((option, i) => (
                  <ChainOption
                    key={i}
                    option={option}
                    isSelected={option?.name === selected?.name}
                    onSelect={() => handleSelect(option)}
                    isOrigin={isOrigin}
                  />
                ))}
                {hasFilteredRemaining && (
                  <div
                    className={`
                    sticky top-0 px-2.5 py-2 mt-2 text-sm
                    text-[--synapse-secondary] bg-[--synapse-surface]
                  `}
                  >
                    Other chains
                  </div>
                )}
                {filteredRemaining.map((option, i) => (
                  <ChainOption
                    key={i}
                    option={option}
                    isSelected={option?.name === selected?.name}
                    onSelect={() => handleSelect(option)}
                    isOrigin={isOrigin}
                  />
                ))}
              </ul>
            ) : (
              <div className="p-2 text-sm break-all">
                No chains found
                <br />
                matching '{filterValue}'.
              </div>
            )
          ) : null}
          {activeTab === 'Target' ? (
            hasFilteredTargets ? (
              <ul className="p-0 m-0">
                {filteredTargets.map((option, i) => (
                  <ChainOption
                    key={i}
                    option={option}
                    isSelected={option?.name === selected?.name}
                    onSelect={() => handleSelect(option)}
                    isOrigin={isOrigin}
                  />
                ))}
              </ul>
            ) : (
              <div className="p-2 text-sm break-all">
                No chains found
                <br />
                matching '{filterValue}'.
              </div>
            )
          ) : null}
        </div>
      )}
    </div>
  )
}