UNPKG

@codinglane/dropdown

Version:
141 lines (140 loc) 7.65 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import React from 'react'; import * as Icons from 'react-feather'; import './Dropdown.css'; import * as Components from '../components'; import * as Contracts from '../contracts'; /** * @param id * The current id of the dropdown. * @param value * The current chosen value for the dropdown, typeof string. * The value is not getting managed inside the component. You have to provide a function to manage the current chosen value. * @param searchable * Set this value to true, if you want to have a dropdown with an input field to search for a specific option * @param className * Set the classname of the dropdown, if you want to set your custom style. * @param closeOnSelect * Set this value to true, if you want that the options of the dropdown are getting closed as soon as you choose an option. * @param fields * These are the possible options for the dropdown. If you want to have grouped dropdown options, set the type of your fields to GroupedDropdownOptions * and set the group tag for all of the fields. * @param placeholder * The placeholder for the dropdown. When the placeholder is set and the value is undefined or not assignable to a field, the placeholder is getting showed. * @param onChange * This is the function to manage the current chosen value for the dropdown. * @param onFocus * This function is getting called on focus of the options menu. * @param onBlur * This function is getting called on blur of the options menu. * @param onFavorizeOption * The on favorize option is getting called as soon as the favorize icon in the option menu is getting clicked. * This dropdown do not manage the favorites on its own. You have to manage the favorites, to see changes in the component. * @param favoriteLabels * The labels for the favorite and non favorite group. * @param style * Custom stylesheet for the dropdown. It is possible to set the bg color, the color (for text & border), the font size and the font family. * @param data-testid * For testing purpose. * @returns {JSX.Element} */ export const Dropdown = ({ id, fields, value, searchable = false, className, closeOnSelect = false, placeholder, onChange, onBlur, onFocus, onFavorizeOption, style, ...props }) => { const dropdown = React.useRef(); const setDropdown = (ref) => (dropdown.current = ref); const input = React.useRef(); const setInput = (ref) => (input.current = ref); const menu = React.useRef(); const setMenu = (ref) => (menu.current = ref); const textContainer = React.useRef(); const setTextContainer = (ref) => (textContainer.current = ref); const text = React.useRef(); const setText = (ref) => (text.current = ref); const [currentVisibleOptions, setCurrentVisibleOptions] = React.useState([]); const grouped = fields.some((fld) => fld.group !== undefined); const favorize = fields.some((fld) => fld.favorite !== undefined); const [active, setActive] = React.useState(false); const [search, setSearch] = React.useState(null); const field = React.useMemo(() => fields.find((fld) => fld.value === value)?.label, [value, fields]); const anchor = React.useMemo(() => { if (!active) return; if (!input.current) return; const height = document.body.clientHeight; const isScrollable = document.body.clientHeight < document.body.scrollHeight; const at = input.current.getBoundingClientRect().bottom + 5; if (input.current.getBoundingClientRect().bottom + Contracts.MENU_MAX_HEIGHT + 10 > height) return { at: height - input.current.getBoundingClientRect().top + 5 - (isScrollable ? Contracts.getScrollbarWidth() : 0), direction: 'UP', }; return { at, direction: 'DOWN' }; }, [active, input.current]); const handleOptionClick = (option) => { onChange(option); setSearch(null); if (!closeOnSelect) return; setActive(false); }; React.useEffect(() => { Contracts.setStyleSheet(style); }, [style]); React.useEffect(() => { if (active && onFocus) onFocus(); if (!active && onBlur) onBlur(); }, [active]); const handleSearch = (event) => setSearch(event.currentTarget.value); const toggle = () => setActive((prev) => !prev); const enterSearch = (event) => { event.persist(); if (event.key.toLowerCase() !== 'enter') return; if (search === null) return; if (currentVisibleOptions.length > 0) onChange(currentVisibleOptions[0]); setSearch(null); toggle(); }; React.useEffect(() => { if (!input.current) return; if (active) input.current.focus(); else { setSearch(null); input.current.blur(); } }, [input.current, active]); React.useEffect(() => { const clickListener = (event) => { if (dropdown.current?.contains(event.target)) return; setActive(false); }; const scrollListener = (event) => { if (menu.current?.contains(event.target)) return; setActive(false); }; document.addEventListener('click', clickListener); document.addEventListener('scroll', scrollListener, true); return () => { document.removeEventListener('click', clickListener); document.removeEventListener('scroll', scrollListener, true); }; }, []); React.useLayoutEffect(() => { if (!textContainer.current || !text.current || !input.current) return; textContainer.current.style.width = `${text.current.clientWidth + 17.5}px`; input.current.style.width = `${text.current.clientWidth - 4.5}px`; }, [text.current, textContainer.current, value]); return (_jsxs("div", { className: `dropdown ${active ? 'active' : ''} ${className ?? ''}`, ref: setDropdown, ...props, children: [_jsxs("div", { className: 'customBase dropdown-search', children: [_jsxs("div", { className: 'dropdown-text'.concat(!searchable ? ' dropdown-readonly' : ''), onClick: toggle, ref: setTextContainer, children: [!search && (_jsx("div", { className: `dropdown-searchtext ${field === undefined ? 'dropdown-placeholder' : ''}`, ref: setText, children: field ?? placeholder })), _jsx(Components.BaseInput, { value: search ?? '', id: id?.concat('input'), onChange: handleSearch, className: 'dropdown-searchinput', autoComplete: 'off', autoCapitalize: 'off', autoCorrect: 'off', disabled: !searchable, ref: setInput, onKeyUp: enterSearch, "data-testid": props['data-testid']?.concat('-input') })] }), _jsx(Icons.ChevronDown, { size: 16, className: 'dropdown-searchicon', onClick: toggle })] }), grouped || favorize ? (_jsx(Components.Grouped, { id: id, onOptionClick: handleOptionClick, onFilteredChange: setCurrentVisibleOptions, options: fields, current: value, anchor: anchor, ref: setMenu, filter: search, onFavorize: onFavorizeOption, grouping: grouped, favorize: favorize, ...props })) : (_jsx(Components.Standard, { id: id, onOptionClick: handleOptionClick, options: fields, current: value, filter: search, anchor: anchor, ref: setMenu, onFilteredChange: setCurrentVisibleOptions, ...props }))] })); };