import { forwardRef, useState } from "react"; import { Flex } from "../flex"; import { Icons } from "../icons"; import { ScrollArea } from "../scroll-area"; import { useInfiniteScroll, useInputDevice } from "../../utils"; import { StyledRoot, StyledWrapper, StyledTrigger, StyledFieldSet, StyledLegend, StyledLabel, StyledValue, StyledIcon, StyledPortal, StyledContent, StyledViewport, StyledGroup, StyledItem, StyledItemText, StyledExtraInfo, StyledIconContainer, StyledExtraContainer, } from "./select.styled"; import { useSelect } from "./use-select"; import type { WithTestId } from "../../types"; export interface SelectItem extends React.ComponentProps { value: string; label: string; extra?: React.ReactNode; } export interface SelectProps extends WithTestId> { /** * The aria-label to be added to the select input trigger container */ ariaLabel?: string; /** * The label text of the select input */ label: string; /** * The placeholder text of the select input */ placeholder?: string; /** * The htmlFor text of the select input */ htmlFor?: string; /** * The options of the select input */ options: Array; /** * When set this state to true, the select input will display in red */ error?: boolean; /** * The extra info to be displayed underneth the select input, for example the error message should be passed to this prop */ info?: string; /** * If true, will apply ellipsis text overflow to info text */ infoEllipsis?: boolean; /** * The element to be rendered at the right of the select input */ rightElement?: React.ReactNode; /** * Sets the positions of the extra element of each option */ extraInListPosition?: "right" | "left"; rootProps?: Omit, "value" | "onChangeValue">; triggerProps?: Omit, "disabled">; contentProps?: Omit, "position">; itemProps?: Omit, "value">; /** * Should the select be disabled? */ disabled?: boolean; /** * This callback is triggered when the last element of the list is reached (starts being visible) */ onLastElementReached?: () => void; } /** * A reusable Select component */ export const Select = forwardRef( // eslint-disable-next-line max-lines-per-function ( { ariaLabel, label, placeholder, htmlFor, options, error, info, infoEllipsis, rightElement, defaultValue, value, onValueChange, onOpenChange, extraInListPosition = "left", rootProps, triggerProps, contentProps, itemProps, disabled, onLastElementReached, css, "data-id": dataId, }, ref ) => { const { handleOnValChange, isFilled, triggerRef, selectedOption, contentRef } = useSelect({ value, onValueChange, defaultValue, options, }); const [lastElement, setLastElement] = useState(null); useInfiniteScroll(lastElement, onLastElementReached); const device = useInputDevice(); return ( {selectedOption ? ( {selectedOption.extra} {selectedOption.label.replace(/ /g, "\u00A0")} ) : ( )} {rightElement} {label && ( {label} )} {label && {label}} {info && ( {info} )} {options.map(({ value, label, extra, ...internalItemProp }, key) => { const isLast = key === options.length - 1; return ( {extra} {label} ); })} ); } ); Select.displayName = "Select";