import React, { useEffect, useRef, useState } from 'react'; import isNull from 'lodash/isNull'; import Button from '@leafygreen-ui/button'; import { css, cx } from '@leafygreen-ui/emotion'; import { usePrevious } from '@leafygreen-ui/hooks'; // leafygreen-ui // @ts-expect-error import PlusIcon from '@leafygreen-ui/icon/dist/Plus'; import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; import { FocusableMenuItem, Menu, MenuItem, MenuSeparator, } from '@leafygreen-ui/menu'; import { useMongoNavContext } from '../../MongoNavContext'; // mongo-nav import { useOnElementClick } from '../../on-element-click-provider'; import { CurrentProjectInterface, Mode, MongoNavInterface, NavElement, ProjectInterface, URLS, } from '../../types'; // mongo-select import MongoSelectInput from '../mongo-select-helpers/Input'; import { onKeyDown } from '../mongo-select-helpers/onKeyDown'; import { menuContainerStyle, menuItemContainerStyle, searchInputStyle, } from '../styles'; import { BaseMongoSelectProps } from '../types'; import { buttonHoverThemeStyle, emptyStateStyle, menuSeparatorStyles, projectButtonContainerStyle, projectMenuContainerStyle, } from './ProjectSelect.styles'; import { ProjectSelectTrigger } from './ProjectSelectTrigger'; // types interface ProjectMongoSelectProps extends BaseMongoSelectProps { data?: Array; current?: CurrentProjectInterface; constructProjectURL: NonNullable; urls: Partial; } interface ProjectFilterResponse { cid: string; gn: string; } function ProjectSelect({ current, mode, urls, data = [], onChange: onChangeProp, constructProjectURL, hosts = {}, admin = false, className, ...rest }: ProjectMongoSelectProps) { const [value, setValue] = useState(''); const [filteredData, setFilteredData] = useState(data); const [isFetching, setIsFetching] = useState(false); const [open, setOpen] = useState(false); const wasOpen = usePrevious(open); const onElementClick = useOnElementClick(); const { darkMode, theme } = useDarkMode(); const { isLoading } = useMongoNavContext(); const buttonRef = useRef(null); const isFiltered = value !== ''; const isAdminSearch = isFiltered && admin && mode === Mode.Production; const isDisabled = isLoading || isNull(current); const renderedData = isFiltered ? filteredData : data; const toggleOpen = () => { setOpen(curr => !curr); if (!open) { setValue(''); } }; const hasOnChangeProp = !!onChangeProp; useEffect(() => { const filterData = () => { const normalizedValue = value.toLowerCase(); const filtered = data?.filter( datum => datum.projectName.toLowerCase().indexOf(normalizedValue) !== -1, ); setFilteredData(filtered); }; // Skip if we're not filtering if (!isFiltered) { return; } // defer all behavior to user-provided filtering if (hasOnChangeProp) { return; } // serverside filtering (admin users only) if (isAdminSearch) { setIsFetching(true); const controller = new AbortController(); const fetchProjectsAsAdmin = (searchTerm = '') => { const queryString = searchTerm ? `?term=${searchTerm}` : ''; const endpointURI = `${hosts.cloud}/user/shared/projects/search${queryString}`; return fetch(endpointURI, { credentials: 'include', mode: 'cors', method: 'GET', signal: controller.signal, }); }; fetchProjectsAsAdmin(value) .then(response => response.json()) .then((response: Array) => { const data = response.map(({ cid: projectId, gn: projectName }) => ({ projectId, projectName, })); setFilteredData(data); setIsFetching(false); }) .catch(console.error); return () => { controller.abort(); }; } // clientside filtering (default) filterData(); }, [isFiltered, hasOnChangeProp, isAdminSearch, hosts.cloud, data, value]); const onChange = (e: React.ChangeEvent) => { const value = e.target.value; setValue(value); if (open === true && wasOpen !== open) { // Opt out of type-checking as we just want to trigger the onElementClick handler onElementClick(NavElement.ProjectNavProjectSelectSearch)(e as any); } return onChangeProp?.({ value, setData: setFilteredData, event: e, }); }; const renderProjectOption = (datum: ProjectInterface) => { const { projectId, projectName } = datum; const isActive = projectId ? projectId === current?.projectId : false; return ( {projectName} ); }; return (
{isAdminSearch && isFetching && ( Searching... )} {!isFetching && renderedData.length === 0 && ( No matches found. )} {renderedData?.map(renderProjectOption)}
  • ); } ProjectSelect.displayName = 'ProjectSelect'; export default ProjectSelect;