'use client'; import { getClassNames, withSchema } from '@websolutespa/bom-core'; import { useCurrentState } from '@websolutespa/bom-mixer-hooks'; import React, { CSSProperties, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { Ellipsis, Flex, getChildsByType } from '../../components'; import { UIComponentWithRef, UIStyledComponentProps } from '../../components/types'; import { getCssResponsive } from '../../components/utils'; import { SelectLegacyIcon } from '../select-legacy/select-legacy-icon'; import { FormControlCss } from '../styles'; import { CustomSelectConfig, CustomSelectContext } from './custom-select-context'; import { CustomSelectDivider } from './custom-select-divider'; import { CustomSelectDropdown } from './custom-select-dropdown'; import { CustomSelectInput } from './custom-select-input'; import { CustomSelectLabel } from './custom-select-label'; import { CustomSelectMultipleValue } from './custom-select-multiple-value'; import { CustomSelectOption } from './custom-select-option'; export type CustomSelectRef = { focus: () => void; blur: () => void; scrollTo?: (options?: ScrollToOptions) => void; }; type Props = { disabled?: boolean; name?: string; value?: string | string[]; initialValue?: string | string[]; placeholder?: React.ReactNode | string; icon?: React.ComponentType; onBlur?: (event: React.FocusEvent) => void; onFocus?: (event: React.FocusEvent) => void; onChange?: (value: string | string[]) => void; pure?: boolean; multiple?: boolean; clearable?: boolean; className?: string; dropdownClassName?: string; dropdownStyle?: CSSProperties; disableMatchWidth?: boolean; onDropdownVisibleChange?: (visible: boolean) => void; getPopupContainer?: () => HTMLElement | null; }; export type CustomSelectProps = UIStyledComponentProps; export type CustomSelectComponent = UIComponentWithRef; const StyledCustomSelect = styled.div` ${FormControlCss} padding: var(--form-control-padding); position: relative; display: inline-flex; justify-content: space-between; align-items: center; overflow: hidden; white-space: nowrap; appearance: none; user-select: none; cursor: pointer; .placeholder { color: inherit; opacity: 0.5; } .value { display: inline-flex; flex: 1; height: 100%; align-items: center; // line-height: 1; padding: 0; margin-right: 1.25em; width: calc(100% - 1.25em); } .icon { pointer-events: none; transition: color 200ms ease, transform 200ms ease; } &.opened { .icon { transform: rotate(180deg); } } &.multiple { padding: calc(var(--form-control-padding) * 0.675); .placeholder { padding: 0.563rem 0; } } &.disabled { .value, .icon { color: var(--color-primary-400); } } ${props => getCssResponsive(props)} `; const CustomSelectBase: CustomSelectComponent = forwardRef>(({ disabled = false, pure = false, multiple = false, clearable = true, className = '', disableMatchWidth = false, onDropdownVisibleChange = () => { }, value: valueProp, initialValue: initialValueProp, icon, children, onChange, placeholder, dropdownClassName, dropdownStyle, getPopupContainer, onBlur, onFocus, id, name, ...props }: React.PropsWithChildren, selectRef) => { const Icon = icon || SelectLegacyIcon; const ref = useRef(null); const inputRef = useRef(null); const dropdownRef = useRef(null); const [visible, setVisible] = useState(false); const [selectFocus, setSelectFocus] = useState(false); const [value, setValue, valueRef] = useCurrentState(() => { if (!multiple) { return typeof initialValueProp === 'undefined' ? '' : initialValueProp; } if (Array.isArray(initialValueProp)) { return initialValueProp; } return typeof initialValueProp === 'undefined' ? [] : [initialValueProp]; }); const isEmpty = useMemo(() => { if (!Array.isArray(value)) { return !value; } return value.length === 0; }, [value]); const updateVisible = useCallback((next: boolean) => { onDropdownVisibleChange(next); setVisible(next); }, [onDropdownVisibleChange]); const updateValue = (next: string) => { setValue(last => { if (!Array.isArray(last)) { return next; } if (!last.includes(next)) { return [...last, next]; } return last.filter(item => item !== next); }); onChange && onChange(valueRef.current as string | string[]); if (!multiple) { updateVisible(false); } }; const initialValue: CustomSelectConfig = useMemo(() => ({ disableAll: disabled, visible, value, ref, updateValue, updateVisible, }), [disabled, visible, value]); const clickHandler = (event: React.MouseEvent) => { event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); event.preventDefault(); if (disabled) { return; } updateVisible(!visible); }; const mouseDownHandler = (event: React.MouseEvent) => { /* istanbul ignore next */ if (visible) { event.preventDefault(); } }; useEffect(() => { if (valueProp === undefined) { return; } setValue(valueProp); }, [setValue, valueProp]); useImperativeHandle(selectRef, () => ({ focus: () => inputRef.current?.focus(), blur: () => inputRef.current?.blur(), scrollTo: options => dropdownRef.current?.scrollTo(options), }), [inputRef, dropdownRef]); const selectedChildren = (() => { const onClear = (value: string) => { // console.log('onClear', value, clearable); if (clearable) { updateValue(value); } }; const values = Array.isArray(value) ? value : [value]; const [options] = getChildsByType(children, CustomSelectOption); return React.Children.map(options, (option: any) => { // !!! any const optionValue = option.props.value; const optionLabel = option.props.children; if (!values.includes(optionValue)) { return null; } if (!multiple) { return optionLabel; } return ( onClear(optionValue)}> {optionLabel} ); }); })(); const onFocus_ = (event: React.FocusEvent) => { onFocus && onFocus(event); setSelectFocus(true); }; const onBlur_ = (event: React.FocusEvent) => { onBlur && onBlur(event); updateVisible(false); setSelectFocus(false); }; const classNames = getClassNames('select', { active: selectFocus || visible, opened: visible, multiple, disabled }, className); return ( {isEmpty && ( {placeholder} )} {value && ( multiple ? {selectedChildren} : {selectedChildren} )} {children} {!pure && ( )} ); }); CustomSelectBase.displayName = 'CustomSelect'; export const CustomSelect = withSchema( CustomSelectBase, { Label: CustomSelectLabel, Divider: CustomSelectDivider, Option: CustomSelectOption, });