UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

485 lines (484 loc) 15.2 kB
"use client"; var _AlignmentHelper; import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js"; import React, { useContext, useRef, useCallback } from 'react'; import clsx from 'clsx'; import { validateDOMAttributes, getStatusState, combineDescribedBy, combineLabelledBy, dispatchCustomElementEvent, convertJsxToString, removeUndefinedProps } from "../../shared/component-helper.js"; import { extendPropsWithContext } from "../../shared/helpers/extendPropsWithContext.js"; import useMountEffect from "../../shared/helpers/useMountEffect.js"; import { useIsomorphicLayoutEffect } from "../../shared/helpers/useIsomorphicLayoutEffect.js"; import useId from "../../shared/helpers/useId.js"; import AlignmentHelper from "../../shared/AlignmentHelper.js"; import { applySpacing } from "../space/SpacingUtils.js"; import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js"; import Suffix from "../../shared/helpers/Suffix.js"; import Icon from "../icon-primary/IconPrimary.js"; import FormLabel from "../form-label/FormLabel.js"; import FormStatus from "../form-status/FormStatus.js"; import Button from "../button/Button.js"; import DrawerList from "../../fragments/drawer-list/DrawerList.js"; import DrawerListContext from "../../fragments/drawer-list/DrawerListContext.js"; import DrawerListProvider from "../../fragments/drawer-list/DrawerListProvider.js"; import { parseContentTitle, getCurrentData } from "../../fragments/drawer-list/DrawerListHelpers.js"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const dropdownDefaultProps = { id: null, title: 'Option Menu', variant: 'secondary', icon: null, iconSize: null, iconPosition: null, arrowPosition: null, label: null, labelDirection: 'vertical', labelSrOnly: null, status: null, statusState: 'error', statusProps: null, statusNoAnimation: null, globalStatus: null, ref: null, buttonRef: null, suffix: null, scrollable: true, focusable: false, maxHeight: null, direction: 'auto', skipPortal: null, portalClass: null, noAnimation: false, noScrollAnimation: false, preventSelection: false, independentWidth: false, size: 'default', align: null, triggerElement: null, data: null, defaultValue: null, value: 'initval', openOnFocus: false, preventClose: false, keepOpen: false, open: false, disabled: null, stretch: null, skeleton: null, className: null, children: null, onOpen: null, onClose: null, onChange: null, onSelect: null }; const DropdownInstance = React.memo(function DropdownInstance({ externalRef, externalButtonRef, ...ownProps }) { const context = useContext(DrawerListContext); const elRef = useRef(null); const wrapperRef = useRef(null); const buttonRef = useRef(null); const focusTimeoutRef = useRef(null); const attributesRef = useRef({}); const setRootRef = useCallback(el => { elRef.current = el; if (typeof externalRef === 'function') { externalRef(el); } else if (externalRef) { externalRef.current = el; } }, [externalRef]); const setButtonRef = useCallback(el => { buttonRef.current = el; if (typeof externalButtonRef === 'function') { externalButtonRef(el); } else if (externalButtonRef) { externalButtonRef.current = el; } }, [externalButtonRef]); const propsWithDefaults = { ...dropdownDefaultProps, ...removeUndefinedProps({ ...ownProps }) }; const propsWithDefaultsRef = useRef(propsWithDefaults); propsWithDefaultsRef.current = propsWithDefaults; useIsomorphicLayoutEffect(() => { if (propsWithDefaults.open) { context.drawerList.setWrapperElement(wrapperRef.current).setVisible(); } }, []); useMountEffect(() => { return () => { if (focusTimeoutRef.current) { clearTimeout(focusTimeoutRef.current); } }; }); const setVisible = useCallback(() => { context.drawerList.setWrapperElement(wrapperRef.current).setVisible(); }, [context.drawerList]); const setHidden = useCallback((...args) => { context.drawerList.setHidden(...args); }, [context.drawerList]); const setFocus = useCallback(args => { if (focusTimeoutRef.current) { clearTimeout(focusTimeoutRef.current); } focusTimeoutRef.current = setTimeout(() => { try { const element = buttonRef.current; if (element && typeof element.focus === 'function') { if (args.preventHideFocus !== true) { element.focus({ preventScroll: true }); } dispatchCustomElementEvent({ props: propsWithDefaultsRef.current }, 'onCloseFocus', { element }); } } catch (e) {} }, 1); }, []); const onFocusHandler = useCallback(() => { if (propsWithDefaults.openOnFocus) { setVisible(); } }, [propsWithDefaults.openOnFocus, setVisible]); const onBlurHandler = useCallback(() => { if (propsWithDefaults.openOnFocus) { setHidden(); } }, [propsWithDefaults.openOnFocus, setHidden]); const onClickHandler = useCallback(() => { if (propsWithDefaults.disabled) { return; } if (!context.drawerList.hidden && context.drawerList.isOpen) { setHidden(); } else { setVisible(); } }, [propsWithDefaults.disabled, context.drawerList.hidden, context.drawerList.isOpen, setHidden, setVisible]); const onTriggerKeyDownHandler = useCallback(e => { switch (e.key) { case 'Enter': case ' ': e.preventDefault(); setVisible(); break; case 'ArrowUp': case 'ArrowDown': e.preventDefault(); setVisible(); break; case 'Escape': setHidden(); break; case 'Home': case 'End': case 'PageDown': case 'PageUp': e.preventDefault(); break; } }, [setVisible, setHidden]); const onCloseHandler = useCallback((args = {}) => { const attributes = attributesRef.current || {}; const res = dispatchCustomElementEvent({ props: propsWithDefaultsRef.current }, 'onClose', { ...args, attributes }); if (res !== false) { setFocus(args); } return res; }, [setFocus]); const onSelectHandler = useCallback(args => { if (parseFloat(args.activeItem) > -1) { const attributes = attributesRef.current || {}; dispatchCustomElementEvent({ props: propsWithDefaultsRef.current }, 'onSelect', { ...args, attributes }); } }, []); const onChangeHandler = useCallback(args => { const attributes = attributesRef.current || {}; dispatchCustomElementEvent({ props: propsWithDefaultsRef.current }, 'onChange', { ...args, attributes }); }, []); const getTitle = (title = null) => { const { data } = context.drawerList; if (data && data.length > 0) { const currentOptionData = getCurrentData(context.drawerList.selectedItem, data); if (currentOptionData) { title = currentOptionData.selectedValue || parseContentTitle(currentOptionData); } } return title; }; const props = extendPropsWithContext(propsWithDefaults, dropdownDefaultProps, { skeleton: context === null || context === void 0 ? void 0 : context.skeleton }, context.getTranslation(propsWithDefaults).Dropdown, pickFormElementProps(context === null || context === void 0 ? void 0 : context.formElement), context.Dropdown); const { label, labelDirection, labelSrOnly, iconSize, size, fixedPosition, enableBodyLock, status, statusState, statusProps, statusNoAnimation, globalStatus, suffix, scrollable, focusable, keepOpen, preventClose, noAnimation, noScrollAnimation, arrowPosition, skipPortal, portalClass, triggerElement: CustomTrigger, independentWidth, preventSelection, maxHeight, defaultValue, className, disabled, stretch, skeleton, variant, title: _title, icon: _icon, align: _align, iconPosition: _iconPosition, openOnFocus: _openOnFocus, data: _data, children: _children, direction: _direction, id: _id, open: _open, value: _value, pageOffset: _pageOffset, observerElement: _observerElement, enableBodyLock: _enableBodyLock, listClass: _listClass, buttonRef: _buttonRef, ref: _ref, onOpen: _onOpen, onClose: _onClose, onFocus: _onFocus, onChange: _onChange, onSelect: _onSelect, onOpenFocus: _onOpenFocus, onCloseFocus: _onCloseFocus, externalRef: _externalRef, externalButtonRef: _externalButtonRef, ...attributes } = props; let { icon, iconPosition, align } = props; const handleAsMenu = preventSelection; const title = getTitle(_title); const isPopupMenu = !title; if (isPopupMenu) { icon = icon || 'chevron_down'; } if (isPopupMenu) { if (iconPosition !== 'right' && align !== 'right') { iconPosition = 'left'; align = 'left'; } } if (independentWidth && iconPosition !== 'left' && !align) { align = 'right'; } const { id, selectedItem, direction, open } = context.drawerList; const showStatus = getStatusState(status); Object.assign(context.drawerList.attributes, validateDOMAttributes(null, attributes)); const mainParams = applySpacing(props, { className: clsx(`dnb-dropdown dnb-dropdown--${direction} dnb-dropdown--icon-position-${iconPosition || 'right'} dnb-dropdown--${align || 'right'} dnb-form-component`, className, open && 'dnb-dropdown--open', labelDirection && `dnb-dropdown--${labelDirection}`, isPopupMenu && 'dnb-dropdown--is-popup', independentWidth && 'dnb-dropdown--independent-width', size && `dnb-dropdown--${size}`, stretch && `dnb-dropdown--stretch`, status && `dnb-dropdown__status--${statusState}`, showStatus && 'dnb-dropdown__form-status') }); const triggerParams = { className: 'dnb-dropdown__trigger', id, disabled, 'aria-haspopup': handleAsMenu ? true : 'listbox', 'aria-expanded': open, ...attributes, onFocus: onFocusHandler, onBlur: onBlurHandler, onClick: onClickHandler, onKeyDown: onTriggerKeyDownHandler }; if (open) { triggerParams['aria-controls'] = `${id}-ul`; } if (showStatus || suffix) { triggerParams['aria-describedby'] = combineDescribedBy(triggerParams, showStatus ? id + '-status' : null, suffix ? id + '-suffix' : null); } if (label) { triggerParams['aria-labelledby'] = combineLabelledBy(triggerParams, id + '-label', id); } validateDOMAttributes(null, mainParams); validateDOMAttributes(ownProps, triggerParams); attributesRef.current = validateDOMAttributes(null, attributes); return _jsxs("span", { ref: setRootRef, ...mainParams, children: [label && _jsx(FormLabel, { id: id + '-label', forId: id, text: label, labelDirection: labelDirection, srOnly: labelSrOnly, disabled: disabled, skeleton: skeleton, onClick: onClickHandler }), _jsxs("span", { className: "dnb-dropdown__inner", ref: wrapperRef, children: [_AlignmentHelper || (_AlignmentHelper = _jsx(AlignmentHelper, {})), _jsx(FormStatus, { show: showStatus, id: id + '-form-status', globalStatus: globalStatus, label: label, textId: id + '-status', text: status, state: statusState, noAnimation: statusNoAnimation, skeleton: skeleton, ...statusProps }), _jsxs("span", { className: "dnb-dropdown__row", children: [_jsxs("span", { className: "dnb-dropdown__shell", children: [CustomTrigger ? (React.createElement(CustomTrigger, triggerParams)) : _jsx(Button, { variant: variant, status: status ? statusState : null, statusState: statusState, icon: false, size: size === 'default' ? 'medium' : size, ref: setButtonRef, customContent: _jsxs(_Fragment, { children: [!isPopupMenu && _jsx("span", { className: "dnb-dropdown__text dnb-button__text", children: _jsx("span", { className: "dnb-dropdown__text__inner", children: title }) }), _jsx("span", { "aria-hidden": true, className: 'dnb-dropdown__icon' + (parseFloat(String(selectedItem)) === 0 ? " dnb-dropdown__icon--first" : ""), children: icon !== false && _jsx(Icon, { icon: icon || 'chevron_down', size: iconSize || (size === 'large' ? 'medium' : 'default') }) })] }), role: "combobox", title: convertJsxToString(title) || undefined, ...triggerParams }), _jsx(DrawerList, { id: id, role: handleAsMenu ? 'menu' : 'listbox', portalClass: portalClass, listClass: 'dnb-dropdown__list' + (variant === 'tertiary' ? " dnb-dropdown__list--tertiary" : ""), value: selectedItem, defaultValue: defaultValue, scrollable: scrollable, focusable: focusable, noAnimation: noAnimation, noScrollAnimation: noScrollAnimation, skipPortal: skipPortal, preventSelection: handleAsMenu, arrowPosition: arrowPosition || iconPosition || 'right', keepOpen: keepOpen, preventClose: preventClose, independentWidth: independentWidth || isPopupMenu, isPopup: isPopupMenu, alignDrawer: align || 'left', fixedPosition: fixedPosition, enableBodyLock: enableBodyLock, disabled: disabled, maxHeight: maxHeight, direction: direction, size: size, onChange: onChangeHandler, onSelect: onSelectHandler, onClose: onCloseHandler })] }), suffix && _jsx(Suffix, { className: "dnb-dropdown__suffix", id: id + '-suffix', context: props, onClick: setHidden, children: suffix })] })] })] }); }); function Dropdown({ ref, buttonRef, ...props }) { const id = useId(props.id); const { preventSelection, children, data } = props; return _jsx(DrawerListProvider, { ...props, id: id, data: data || children, open: false, tagName: "dnb-dropdown", ignoreEvents: false, preventSelection: preventSelection, children: _jsx(DropdownInstance, { ...props, id: id, externalRef: ref, externalButtonRef: buttonRef }) }); } Dropdown.HorizontalItem = DrawerList.HorizontalItem; withComponentMarkers(Dropdown, { _formElement: true, _supportsSpacingProps: true }); export default Dropdown; //# sourceMappingURL=Dropdown.js.map