import { useMemo, useRef, useState } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { IconChevronDown } from 'twenty-ui'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { SelectHotkeyScope } from '../types/SelectHotkeyScope'; export type SelectOption = { value: Value; label: string; Icon?: IconComponent; }; export type SelectProps = { className?: string; disabled?: boolean; disableBlur?: boolean; dropdownId: string; dropdownWidth?: `${string}px` | 'auto' | number; emptyOption?: SelectOption; fullWidth?: boolean; label?: string; onChange?: (value: Value) => void; onBlur?: () => void; options: SelectOption[]; value?: Value; withSearchInput?: boolean; }; const StyledContainer = styled.div<{ fullWidth?: boolean }>` width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; `; const StyledControlContainer = styled.div<{ disabled?: boolean }>` align-items: center; background-color: ${({ theme }) => theme.background.transparent.lighter}; border: 1px solid ${({ theme }) => theme.border.color.medium}; box-sizing: border-box; border-radius: ${({ theme }) => theme.border.radius.sm}; color: ${({ disabled, theme }) => disabled ? theme.font.color.tertiary : theme.font.color.primary}; cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; display: flex; gap: ${({ theme }) => theme.spacing(1)}; height: ${({ theme }) => theme.spacing(8)}; justify-content: space-between; padding: 0 ${({ theme }) => theme.spacing(2)}; `; const StyledLabel = styled.span` color: ${({ theme }) => theme.font.color.light}; display: block; font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; margin-bottom: ${({ theme }) => theme.spacing(1)}; `; const StyledControlLabel = styled.div` align-items: center; display: flex; gap: ${({ theme }) => theme.spacing(1)}; `; const StyledIconChevronDown = styled(IconChevronDown)<{ disabled?: boolean }>` color: ${({ disabled, theme }) => disabled ? theme.font.color.extraLight : theme.font.color.tertiary}; `; export const Select = ({ className, disabled: disabledFromProps, disableBlur = false, dropdownId, dropdownWidth = 176, emptyOption, fullWidth, label, onChange, onBlur, options, value, withSearchInput, }: SelectProps) => { const selectContainerRef = useRef(null); const theme = useTheme(); const [searchInputValue, setSearchInputValue] = useState(''); const selectedOption = options.find(({ value: key }) => key === value) || options[0] || emptyOption; const filteredOptions = useMemo( () => searchInputValue ? options.filter(({ label }) => label.toLowerCase().includes(searchInputValue.toLowerCase()), ) : options, [options, searchInputValue], ); const isDisabled = disabledFromProps || options.length <= 1; const { closeDropdown } = useDropdown(dropdownId); const { useListenClickOutside } = useClickOutsideListener(dropdownId); useListenClickOutside({ refs: [selectContainerRef], callback: () => { closeDropdown(); }, }); const selectControl = ( {!!selectedOption?.Icon && ( )} {selectedOption?.label} ); return ( {!!label && {label}} {isDisabled ? ( selectControl ) : ( {!!withSearchInput && ( setSearchInputValue(event.target.value)} /> )} {!!withSearchInput && !!filteredOptions.length && ( )} {!!filteredOptions.length && ( {filteredOptions.map((option) => ( { onChange?.(option.value); onBlur?.(); closeDropdown(); }} /> ))} )} } dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }} /> )} ); };