@razorpay/blade
Version:
The Design System that powers Razorpay
310 lines (302 loc) • 15.8 kB
JavaScript
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
import React__default, { useCallback } from 'react';
import { VariableSizeList } from 'react-window';
import { StyledListBoxWrapper } from './styles/StyledListBoxWrapper.web.js';
import { getActionListPadding, actionListMaxHeight } from './styles/getBaseListBoxWrapperStyles.js';
import { componentIds } from './componentIds.js';
import { ActionListSectionTitle } from './ActionListItem.js';
import { useBottomSheetContext } from '../BottomSheet/BottomSheetContext.js';
import '../../utils/assignWithoutSideEffects/index.js';
import '../../utils/makeAccessible/index.js';
import '../../utils/makeAnalyticsAttribute/index.js';
import { useIsMobile } from '../../utils/useIsMobile.js';
import { actionListSectionTitleHeight, actionListDividerHeight, getActionListItemHeight } from '../BaseMenu/BaseMenuItem/tokens.js';
import '../../utils/index.js';
import { useDropdown } from '../Dropdown/useDropdown.js';
import { dropdownComponentIds } from '../Dropdown/dropdownComponentIds.js';
import '../../utils/isValidAllowedChildren/index.js';
import '../Divider/index.js';
import { jsx } from 'react/jsx-runtime';
import { makeAccessible } from '../../utils/makeAccessible/makeAccessible.web.js';
import { makeAnalyticsAttribute } from '../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js';
import { assignWithoutSideEffects } from '../../utils/assignWithoutSideEffects/assignWithoutSideEffects.js';
import { getComponentId } from '../../utils/isValidAllowedChildren/isValidAllowedChildren.js';
import { Divider } from '../Divider/Divider.js';
import useTheme from '../BladeProvider/useTheme.js';
var _excluded = ["childrenWithId", "actionListItemWrapperRole", "isMultiSelectable"],
_excluded2 = ["style"],
_excluded3 = ["childrenWithId", "actionListItemWrapperRole", "isMultiSelectable"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var _ActionListBox = /*#__PURE__*/React__default.forwardRef(function (_ref, ref) {
var childrenWithId = _ref.childrenWithId,
actionListItemWrapperRole = _ref.actionListItemWrapperRole,
isMultiSelectable = _ref.isMultiSelectable,
rest = _objectWithoutProperties(_ref, _excluded);
var _useBottomSheetContex = useBottomSheetContext(),
isInBottomSheet = _useBottomSheetContex.isInBottomSheet;
return /*#__PURE__*/jsx(StyledListBoxWrapper, _objectSpread(_objectSpread(_objectSpread({
isInBottomSheet: isInBottomSheet,
ref: ref
}, makeAccessible({
role: actionListItemWrapperRole,
multiSelectable: actionListItemWrapperRole === 'listbox' ? isMultiSelectable : undefined
})), makeAnalyticsAttribute(rest)), {}, {
children: childrenWithId
}));
});
var ActionListBox = /*#__PURE__*/assignWithoutSideEffects(/*#__PURE__*/React__default.memo(_ActionListBox), {
displayName: 'ActionListBox'
});
/**
* get the height of the item based on the componentId
*/
var getItemHeight = function getItemHeight(_ref2) {
var _itemData$index;
var index = _ref2.index,
itemData = _ref2.itemData,
actionListItemHeight = _ref2.actionListItemHeight;
if (getComponentId(itemData[index]) === componentIds.ActionListItem) {
return actionListItemHeight;
}
if (getComponentId(itemData[index]) === componentIds.ActionListSectionTitle) {
return actionListSectionTitleHeight;
}
// @ts-expect-error: key does exist
if ((_itemData$index = itemData[index]) !== null && _itemData$index !== void 0 && _itemData$index.key.includes('divider')) {
return actionListDividerHeight;
}
return 0;
};
/**
* Returns the height of item and height of container based on theme and device
*/
var getVirtualItemParams = function getVirtualItemParams(_ref3) {
var theme = _ref3.theme,
isMobile = _ref3.isMobile,
itemCount = _ref3.itemCount,
itemData = _ref3.itemData;
var itemHeightResponsive = getActionListItemHeight(theme);
var actionListItemVerticalMargin = theme.spacing[1] * 2;
var actionListPadding = getActionListPadding(theme);
var actionListItemHeight = (isMobile ? itemHeightResponsive.itemHeightMobile : itemHeightResponsive.itemHeightDesktop) + actionListItemVerticalMargin;
var shouldCalculateMinimumHeight = itemCount <= 10;
var actionListBoxHeight = actionListMaxHeight - actionListPadding * 2;
var actionListBoxMinHeight = shouldCalculateMinimumHeight ? itemData.reduce(function (acc, _, index) {
var itemHeight = getItemHeight({
index: index,
itemData: itemData,
actionListItemHeight: actionListItemHeight
});
return acc + itemHeight;
}, 0) : actionListBoxHeight;
var finalActionListBoxHeight = Math.min(actionListBoxHeight, actionListBoxMinHeight);
return {
actionListItemHeight: actionListItemHeight,
actionListBoxHeight: finalActionListBoxHeight
};
};
/**
* Takes the children (ActionListItem) and returns the filtered items based on `filteredValues` state
*/
var useFilteredItems = function useFilteredItems(children) {
var childrenArray = React__default.Children.toArray(children); // Convert children to an array
var _useDropdown = useDropdown(),
filteredValues = _useDropdown.filteredValues,
hasAutoCompleteInHeader = _useDropdown.hasAutoCompleteInHeader,
dropdownTriggerer = _useDropdown.dropdownTriggerer;
var items = React__default.useMemo(function () {
var hasAutoComplete = hasAutoCompleteInHeader || dropdownTriggerer === dropdownComponentIds.triggers.AutoComplete;
if (!hasAutoComplete) {
return childrenArray;
}
var filteredItems = childrenArray.reduce(function (acc, item, index) {
if (getComponentId(item) === componentIds.ActionListSection) {
var sectionTitle = /*#__PURE__*/jsx(ActionListSectionTitle, {
// @ts-expect-error: props does exist
title: item === null || item === void 0 ? void 0 : item.props.title,
isInsideVirtualizedList: true
}, index);
// @ts-expect-error: props does exist
var sectionChildren = item === null || item === void 0 ? void 0 : item.props.children;
var divider = index !== childrenArray.length - 1 ? /*#__PURE__*/jsx(Divider, {
marginX: "spacing.3",
marginY: "spacing.1"
}, "divider-".concat(index)) : null;
var filteredSectionChildren = sectionChildren.filter(function (item) {
return filteredValues.includes(item.props.value);
});
if (filteredSectionChildren.length !== 0) {
acc.push.apply(acc, [sectionTitle].concat(_toConsumableArray(filteredSectionChildren), [divider]));
}
} else {
// @ts-expect-error: props does exist
var value = item === null || item === void 0 ? void 0 : item.props.value;
if (filteredValues.includes(value)) {
acc.push(item);
}
}
return acc;
}, []);
return filteredItems;
}, [filteredValues, hasAutoCompleteInHeader, dropdownTriggerer, childrenArray]);
return {
itemData: items,
itemCount: items.length
};
};
/**
* Custom outer element for VirtualizedList that enables scrolling inside BottomSheet.
* - data-allow-scroll: tells BottomSheet's useDrag to not capture touch events on this element
* - data-body-scroll-lock-ignore: allows this element to scroll even when body scroll is locked
* - touchAction: 'pan-y' enables vertical touch scrolling (overrides parent's 'none')
* - overscrollBehavior: 'contain' prevents scroll chaining to parent elements
*/
var BottomSheetCompatibleOuterElement = /*#__PURE__*/React__default.forwardRef(function (_ref4, ref) {
var style = _ref4.style,
props = _objectWithoutProperties(_ref4, _excluded2);
return /*#__PURE__*/jsx("div", _objectSpread(_objectSpread({
ref: ref
}, props), {}, {
"data-allow-scroll": "true",
"data-body-scroll-lock-ignore": "true",
style: _objectSpread(_objectSpread({}, style), {}, {
touchAction: 'pan-y',
overscrollBehavior: 'contain'
})
}));
});
var VirtualListItem = /*#__PURE__*/React__default.memo(function (_ref5) {
var index = _ref5.index,
style = _ref5.style,
data = _ref5.data,
onVirtualizedFocus = _ref5.onVirtualizedFocus;
var currentItem = data[index];
if (/*#__PURE__*/React__default.isValidElement(currentItem) && getComponentId(currentItem) === componentIds.ActionListItem) {
// Clone the element passed via `data` and add the `_virtualizedIndex` prop
var elementWithIndex = /*#__PURE__*/React__default.cloneElement(currentItem, {
_virtualizedIndex: index,
_onVirtualizedFocus: onVirtualizedFocus
});
return /*#__PURE__*/jsx("div", {
style: style,
children: elementWithIndex
});
}
return /*#__PURE__*/jsx("div", {
style: style,
children: data[index]
});
}, function (prevProps, nextProps) {
// Custom comparison function to determine if component should update
return prevProps.index === nextProps.index && prevProps.style === nextProps.style && prevProps.data[prevProps.index] === nextProps.data[nextProps.index];
});
var _ActionListVirtualizedBox = /*#__PURE__*/React__default.forwardRef(function (_ref6, ref) {
var childrenWithId = _ref6.childrenWithId,
actionListItemWrapperRole = _ref6.actionListItemWrapperRole,
isMultiSelectable = _ref6.isMultiSelectable,
rest = _objectWithoutProperties(_ref6, _excluded3);
var virtualizedListRef = React__default.useRef(null);
var _React$useState = React__default.useState(0),
_React$useState2 = _slicedToArray(_React$useState, 2),
visibleStartIndex = _React$useState2[0],
setVisibleStartIndex = _React$useState2[1];
var _React$useState3 = React__default.useState(0),
_React$useState4 = _slicedToArray(_React$useState3, 2),
visibleStopIndex = _React$useState4[0],
setVisibleStopIndex = _React$useState4[1];
var items = React__default.Children.toArray(childrenWithId); // Convert children to an array
var _useBottomSheetContex2 = useBottomSheetContext(),
isInBottomSheet = _useBottomSheetContex2.isInBottomSheet;
var _useFilteredItems = useFilteredItems(items),
itemData = _useFilteredItems.itemData,
itemCount = _useFilteredItems.itemCount;
var isMobile = useIsMobile();
var _useTheme = useTheme(),
theme = _useTheme.theme;
var _React$useMemo = React__default.useMemo(function () {
return getVirtualItemParams({
theme: theme,
isMobile: isMobile,
itemCount: itemCount,
itemData: itemData
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[theme.name, isMobile, itemCount, itemData]),
actionListItemHeight = _React$useMemo.actionListItemHeight,
actionListBoxHeight = _React$useMemo.actionListBoxHeight;
React__default.useEffect(function () {
var _virtualizedListRef$c, _virtualizedListRef$c2;
virtualizedListRef === null || virtualizedListRef === void 0 || (_virtualizedListRef$c = virtualizedListRef.current) === null || _virtualizedListRef$c === void 0 || _virtualizedListRef$c.resetAfterIndex(0);
virtualizedListRef === null || virtualizedListRef === void 0 || (_virtualizedListRef$c2 = virtualizedListRef.current) === null || _virtualizedListRef$c2 === void 0 || _virtualizedListRef$c2.scrollToItem(0);
}, [itemCount]);
return /*#__PURE__*/jsx(StyledListBoxWrapper, _objectSpread(_objectSpread(_objectSpread({
isInBottomSheet: isInBottomSheet
// in case of virtualized list, we only render visible items. so css will hide divider for every last item visible. instead of hiding the last divider of the list.
,
ref: ref
}, makeAccessible({
role: actionListItemWrapperRole,
multiSelectable: actionListItemWrapperRole === 'listbox' ? isMultiSelectable : undefined
})), makeAnalyticsAttribute(rest)), {}, {
children: /*#__PURE__*/jsx(VariableSizeList, {
ref: virtualizedListRef,
height: actionListBoxHeight,
width: "100%",
itemSize: function itemSize(index) {
return getItemHeight({
index: index,
itemData: itemData,
actionListItemHeight: actionListItemHeight
});
},
itemCount: itemCount,
itemData: itemData,
itemKey: function itemKey(index) {
var _ref7, _itemData$index$props, _itemData$index2, _itemData$index3, _itemData$index4;
return (// @ts-expect-error: props does exist
(_ref7 = (_itemData$index$props = (_itemData$index2 = itemData[index]) === null || _itemData$index2 === void 0 ? void 0 : _itemData$index2.props.value) !== null && _itemData$index$props !== void 0 ? _itemData$index$props : // @ts-expect-error: props does exist
(_itemData$index3 = itemData[index]) === null || _itemData$index3 === void 0 ? void 0 : _itemData$index3.props.title) !== null && _ref7 !== void 0 ? _ref7 : // @ts-expect-error: props does exist
(_itemData$index4 = itemData[index]) === null || _itemData$index4 === void 0 ? void 0 : _itemData$index4.props.key
);
},
onItemsRendered: function onItemsRendered(_ref8) {
var visibleStartIndex = _ref8.visibleStartIndex,
visibleStopIndex = _ref8.visibleStopIndex;
setVisibleStartIndex(visibleStartIndex);
setVisibleStopIndex(visibleStopIndex);
},
outerElementType: isInBottomSheet ? BottomSheetCompatibleOuterElement : undefined,
children: useCallback(function (_ref9) {
var index = _ref9.index,
style = _ref9.style,
data = _ref9.data;
return /*#__PURE__*/jsx(VirtualListItem, {
index: index,
style: style,
data: data,
onVirtualizedFocus: function onVirtualizedFocus(index) {
var _virtualizedListRef$c3, _virtualizedListRef$c4;
// We need scroll Direction to determine the index to focus
var scrollDirection = Math.round((visibleStartIndex + visibleStopIndex) / 2) > index ? 'top' : 'bottom';
virtualizedListRef === null || virtualizedListRef === void 0 || (_virtualizedListRef$c3 = virtualizedListRef.current) === null || _virtualizedListRef$c3 === void 0 || _virtualizedListRef$c3.resetAfterIndex(0);
/**
* we are scrolling to the item which is 3 items away from the current item.
* since we can have 2 item sectoin header and divider which are not focusable.
*/
virtualizedListRef === null || virtualizedListRef === void 0 || (_virtualizedListRef$c4 = virtualizedListRef.current) === null || _virtualizedListRef$c4 === void 0 || _virtualizedListRef$c4.scrollToItem(index + (scrollDirection === 'top' ? -3 : 3), 'smart');
}
});
}, [visibleStartIndex, visibleStopIndex])
})
}));
});
var ActionListVirtualizedBox = /*#__PURE__*/assignWithoutSideEffects(/*#__PURE__*/React__default.memo(_ActionListVirtualizedBox), {
displayName: 'ActionListVirtualizedBox'
});
export { ActionListBox, ActionListVirtualizedBox };
//# sourceMappingURL=ActionListBox.web.js.map