@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
366 lines (355 loc) • 16.4 kB
JavaScript
"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