UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

366 lines (355 loc) • 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _react = _interopRequireWildcard(require("react")); var _element = require("../../hooks/element"); var _useKeyboardFocusHighlighting = require("../../hooks/useKeyboardFocusHighlighting"); var _environment = require("../../utils/environment"); var _ContextMenu = _interopRequireDefault(require("../context-menu/ContextMenu")); var _ActionButton = _interopRequireDefault(require("./action-button/ActionButton")); var _MultiActionButton = require("./MultiActionButton.styles"); var _MultiActionButton2 = require("./MultiActionButton.utils"); var _MultiActionButton3 = require("./MultiActionButton.types"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const SECONDARY_CONTEXT_MENU_ACTION = { icon: 'fa fa-chevron-down', label: undefined }; const createMeasuredMultiActionButtonContent = ({ backgroundColor, gapColor, height, primaryAction, secondaryAction }) => /*#__PURE__*/_react.default.createElement(_MultiActionButton.StyledMultiActionButton, { style: { width: 'fit-content' } }, /*#__PURE__*/_react.default.createElement(_ActionButton.default, { action: primaryAction, actionType: "primary", backgroundColor: primaryAction.backgroundColor ?? backgroundColor, isCollapsed: false, isDisabled: false, isShrunk: false, isSolo: !secondaryAction, showLabel: true, shouldUseContentWidth: true, height: height }), secondaryAction && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_MultiActionButton.StyledSeparator, { $gapColor: gapColor, $isHidden: false }), /*#__PURE__*/_react.default.createElement(_ActionButton.default, { action: secondaryAction, actionType: "secondary", backgroundColor: secondaryAction.backgroundColor ?? backgroundColor, isCollapsed: false, isDisabled: false, isExpanded: false, isHidden: false, showLabel: false, shouldUseContentWidth: true, height: height }))); /** * Multi-action button with optional secondary action that can expand on hover/click. */ const MultiActionButton = ({ backgroundColor, className, extendedTimeoutMs = 3000, gapColor, height = _MultiActionButton3.MultiActionButtonHeight.Medium, isCollapsed = false, isDisabled = false, primaryAction, secondaryAction, secondaryContextMenu, shouldAutoCollapse = false, shouldUseFullWidth, shouldEnableKeyboardHighlighting, width }) => { const [isExtendedByClick, setIsExtendedByClick] = (0, _react.useState)(false); const [isSecondaryExpanded, setIsSecondaryExpanded] = (0, _react.useState)(false); const [isSecondaryHovered, setIsSecondaryHovered] = (0, _react.useState)(false); const [isSecondaryFocused, setIsSecondaryFocused] = (0, _react.useState)(false); const autoCollapseTimeoutRef = (0, _react.useRef)(null); const multiActionButtonRef = (0, _react.useRef)(null); const secondaryContextMenuRef = (0, _react.useRef)(null); const [autoCollapseMode, setAutoCollapseMode] = (0, _react.useState)(_MultiActionButton2.MultiActionButtonAutoCollapseMode.Expanded); const isTouch = (0, _environment.useIsTouch)(); const parentSize = (0, _element.useElementSize)(multiActionButtonRef, { shouldUseParentElement: true }); const hasSecondaryContextMenu = Boolean(secondaryContextMenu === null || secondaryContextMenu === void 0 ? void 0 : secondaryContextMenu.length); const secondaryTriggerAction = hasSecondaryContextMenu ? SECONDARY_CONTEXT_MENU_ACTION : secondaryAction; const hasExpandableSecondaryAction = Boolean(secondaryAction) && !hasSecondaryContextMenu; const hasSecondaryAction = Boolean(secondaryTriggerAction); const shouldUseContentWidth = !width && !shouldUseFullWidth; const shouldShowKeyboardHighlighting = (0, _useKeyboardFocusHighlighting.useKeyboardFocusHighlighting)(shouldEnableKeyboardHighlighting && !isDisabled); const expandedMeasuredContent = (0, _react.useMemo)(() => createMeasuredMultiActionButtonContent({ backgroundColor, gapColor, height, primaryAction, secondaryAction: secondaryTriggerAction }), [backgroundColor, gapColor, height, primaryAction, secondaryTriggerAction]); const { measuredElement, width: expandedMeasuredWidth } = (0, _element.useMeasuredClone)({ content: expandedMeasuredContent }); const availableWidth = (0, _react.useMemo)(() => { const parentWidth = parentSize === null || parentSize === void 0 ? void 0 : parentSize.width; if (typeof width === 'number') { return typeof parentWidth === 'number' ? Math.min(parentWidth, width) : width; } return parentWidth; }, [parentSize === null || parentSize === void 0 ? void 0 : parentSize.width, width]); (0, _react.useEffect)(() => { if (!shouldAutoCollapse || isCollapsed) { setAutoCollapseMode(_MultiActionButton2.MultiActionButtonAutoCollapseMode.Expanded); return; } if (expandedMeasuredWidth <= 0) { return; } setAutoCollapseMode(previousMode => (0, _MultiActionButton2.getMultiActionButtonAutoCollapseMode)({ availableWidth, expandedWidth: expandedMeasuredWidth, hasSecondaryAction, height, previousMode })); }, [availableWidth, expandedMeasuredWidth, hasSecondaryAction, height, isCollapsed, shouldAutoCollapse]); const isAutoIconsOnly = shouldAutoCollapse && autoCollapseMode === _MultiActionButton2.MultiActionButtonAutoCollapseMode.IconsOnly; const isAutoPrimaryOnly = shouldAutoCollapse && autoCollapseMode === _MultiActionButton2.MultiActionButtonAutoCollapseMode.PrimaryOnly; const shouldKeepFullWidth = Boolean(shouldUseFullWidth); const shouldHideSecondaryForAutoCollapse = hasSecondaryAction && isAutoPrimaryOnly; const effectiveIsCollapsed = isCollapsed || !shouldKeepFullWidth && isAutoPrimaryOnly; const resolvedSecondaryAction = hasSecondaryAction ? secondaryTriggerAction : undefined; const isSecondaryHidden = effectiveIsCollapsed || shouldHideSecondaryForAutoCollapse; const shouldPreventSecondaryExpansion = isAutoIconsOnly || isSecondaryHidden; const shouldKeepPrimaryLabelVisible = shouldKeepFullWidth && shouldAutoCollapse; const minimumPrimaryLabelVisibleWidth = (0, _MultiActionButton2.getMinimumPrimaryLabelVisibleWidth)({ hasVisibleSecondaryAction: hasSecondaryAction, height }); const shouldHidePrimaryLabelForMinimumWidth = shouldKeepPrimaryLabelVisible && typeof availableWidth === 'number' && availableWidth <= minimumPrimaryLabelVisibleWidth; const resolvedWidth = (0, _react.useMemo)(() => { if (shouldKeepFullWidth) { return '100%'; } if (effectiveIsCollapsed) { return height; } return width ?? 'fit-content'; }, [effectiveIsCollapsed, height, shouldKeepFullWidth, width]); const secondaryContextMenuTriggerStyle = (0, _MultiActionButton2.getSecondaryContextMenuTriggerStyle)({ height, isCollapsed: isSecondaryHidden, isExpanded: false, shouldUseContentWidth }); /** * Clears the current auto-collapse timer without changing visual state. */ const clearAutoCollapseTimeout = (0, _react.useCallback)(() => { if (autoCollapseTimeoutRef.current) { window.clearTimeout(autoCollapseTimeoutRef.current); autoCollapseTimeoutRef.current = null; } }, []); /** * Clears and restarts the auto-collapse timer used after click-triggered expansion. */ const resetAutoCollapseTimeout = (0, _react.useCallback)(() => { clearAutoCollapseTimeout(); if (extendedTimeoutMs <= 0) { return; } autoCollapseTimeoutRef.current = window.setTimeout(() => { setIsSecondaryExpanded(false); setIsExtendedByClick(false); }, extendedTimeoutMs); }, [clearAutoCollapseTimeout, extendedTimeoutMs]); /** * Expands the secondary action and remembers that it originated from a click. */ const expandSecondaryByClick = (0, _react.useCallback)(() => { setIsSecondaryExpanded(true); setIsExtendedByClick(true); resetAutoCollapseTimeout(); }, [resetAutoCollapseTimeout]); /** * Cleanup timers on unmount. */ (0, _react.useEffect)(() => () => { clearAutoCollapseTimeout(); }, [clearAutoCollapseTimeout]); /** * Collapsing the control should also reset any temporary expansion state. */ (0, _react.useEffect)(() => { if (effectiveIsCollapsed || shouldPreventSecondaryExpansion) { clearAutoCollapseTimeout(); setIsSecondaryExpanded(false); setIsExtendedByClick(false); setIsSecondaryHovered(false); setIsSecondaryFocused(false); } }, [clearAutoCollapseTimeout, effectiveIsCollapsed, shouldPreventSecondaryExpansion]); /** * Handler for the primary action button. */ const handlePrimaryClick = (0, _react.useCallback)(event => { var _primaryAction$onClic; if (isDisabled || primaryAction.isDisabled) { return; } const payload = { action: 'primary', event, isExtended: isSecondaryExpanded, isTouch }; (_primaryAction$onClic = primaryAction.onClick) === null || _primaryAction$onClic === void 0 || _primaryAction$onClic.call(primaryAction, payload); }, [isDisabled, isSecondaryExpanded, isTouch, primaryAction]); /** * Handler for the secondary action button. */ const handleSecondaryClick = (0, _react.useCallback)(event => { var _resolvedSecondaryAct; if (!resolvedSecondaryAction || isSecondaryHidden || isDisabled || resolvedSecondaryAction.isDisabled) { return; } if (hasSecondaryContextMenu) { var _secondaryContextMenu; (_secondaryContextMenu = secondaryContextMenuRef.current) === null || _secondaryContextMenu === void 0 || _secondaryContextMenu.show(); return; } const payload = { action: 'secondary', event, isExtended: isSecondaryExpanded, isTouch }; (_resolvedSecondaryAct = resolvedSecondaryAction.onClick) === null || _resolvedSecondaryAct === void 0 || _resolvedSecondaryAct.call(resolvedSecondaryAction, payload); if (!shouldPreventSecondaryExpansion) { expandSecondaryByClick(); } }, [expandSecondaryByClick, hasSecondaryContextMenu, isSecondaryHidden, isDisabled, isSecondaryExpanded, isTouch, resolvedSecondaryAction, shouldPreventSecondaryExpansion]); /** * Desktop hover behavior keeps the secondary action expanded while hovered. */ const handleSecondaryMouseEnter = (0, _react.useCallback)(() => { if (!secondaryAction || isSecondaryHidden || isTouch || isDisabled || shouldPreventSecondaryExpansion || secondaryAction.isDisabled) { return; } setIsSecondaryHovered(true); if (!isExtendedByClick) { setIsSecondaryExpanded(true); } }, [isSecondaryHidden, isDisabled, isExtendedByClick, isTouch, secondaryAction, shouldPreventSecondaryExpansion]); const handleSecondaryMouseLeave = (0, _react.useCallback)(() => { if (isTouch) { return; } setIsSecondaryHovered(false); if (!isExtendedByClick && !effectiveIsCollapsed) { setIsSecondaryExpanded(false); } }, [effectiveIsCollapsed, isExtendedByClick, isTouch]); const handleSecondaryFocus = (0, _react.useCallback)(() => { if (!secondaryAction || isSecondaryHidden || isDisabled || shouldPreventSecondaryExpansion || secondaryAction.isDisabled) { return; } setIsSecondaryFocused(true); if (!isExtendedByClick) { setIsSecondaryExpanded(true); } }, [isDisabled, isExtendedByClick, isSecondaryHidden, secondaryAction, shouldPreventSecondaryExpansion]); const handleSecondaryBlur = (0, _react.useCallback)(() => { setIsSecondaryFocused(false); if (!isExtendedByClick && !effectiveIsCollapsed) { setIsSecondaryExpanded(false); } }, [effectiveIsCollapsed, isExtendedByClick]); /** * Secondary label is visible when expanded or when hovered/focused. */ const isSecondaryLabelVisible = isSecondaryExpanded || !isTouch && isSecondaryHovered || isSecondaryFocused; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, measuredElement, /*#__PURE__*/_react.default.createElement(_MultiActionButton.StyledMultiActionButton, { className: (0, _clsx.default)('beta-chayns-multi-action', className), ref: multiActionButtonRef, style: { maxWidth: '100%', width: resolvedWidth } }, /*#__PURE__*/_react.default.createElement(_ActionButton.default, { action: primaryAction, actionType: "primary", backgroundColor: primaryAction.backgroundColor ?? backgroundColor, isCollapsed: effectiveIsCollapsed, isDisabled: isDisabled, isShrunk: hasSecondaryAction && (!shouldKeepFullWidth && isAutoIconsOnly || isSecondaryExpanded), isSolo: !hasSecondaryAction || isSecondaryHidden || effectiveIsCollapsed, onClick: handlePrimaryClick, showLabel: !effectiveIsCollapsed && !shouldHidePrimaryLabelForMinimumWidth && (shouldKeepPrimaryLabelVisible || !isAutoIconsOnly) && (shouldKeepPrimaryLabelVisible || !shouldHideSecondaryForAutoCollapse) && (!hasExpandableSecondaryAction || !isSecondaryExpanded), shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting, shouldUseContentWidth: shouldUseContentWidth, height: height }), hasSecondaryAction && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_MultiActionButton.StyledSeparator, { $gapColor: gapColor, $isHidden: isSecondaryHidden }), hasSecondaryContextMenu ? /*#__PURE__*/_react.default.createElement(_ContextMenu.default, { items: secondaryContextMenu ?? [], ref: secondaryContextMenuRef, shouldDisableClick: true, shouldUseDefaultTriggerStyles: false, shouldHidePopupArrow: true, yOffset: -6, style: secondaryContextMenuTriggerStyle }, /*#__PURE__*/_react.default.createElement(_ActionButton.default, { action: resolvedSecondaryAction ?? SECONDARY_CONTEXT_MENU_ACTION, actionType: "secondary", backgroundColor: (resolvedSecondaryAction === null || resolvedSecondaryAction === void 0 ? void 0 : resolvedSecondaryAction.backgroundColor) ?? backgroundColor, isCollapsed: false, isDisabled: isDisabled, isExpanded: false, isHidden: isSecondaryHidden, key: "multi-action-secondary-context-menu", onClick: handleSecondaryClick, showLabel: false, shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting, shouldUseContentWidth: shouldUseContentWidth, height: height })) : /*#__PURE__*/_react.default.createElement(_ActionButton.default, { action: resolvedSecondaryAction ?? secondaryAction ?? SECONDARY_CONTEXT_MENU_ACTION, actionType: "secondary", backgroundColor: (resolvedSecondaryAction === null || resolvedSecondaryAction === void 0 ? void 0 : resolvedSecondaryAction.backgroundColor) ?? backgroundColor, isCollapsed: false, isDisabled: isDisabled, isExpanded: !isAutoIconsOnly && isSecondaryExpanded, isHidden: isSecondaryHidden, key: "multi-action-secondary-button", onClick: handleSecondaryClick, onMouseEnter: handleSecondaryMouseEnter, onMouseLeave: handleSecondaryMouseLeave, onFocus: handleSecondaryFocus, onBlur: handleSecondaryBlur, showLabel: !isSecondaryHidden && !isAutoIconsOnly && isSecondaryLabelVisible, shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting, shouldUseContentWidth: shouldUseContentWidth, height: height })))); }; MultiActionButton.displayName = 'MultiActionButton'; var _default = exports.default = MultiActionButton; //# sourceMappingURL=MultiActionButton.js.map