UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

315 lines (306 loc) 12.9 kB
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import _slicedToArray from '@babel/runtime/helpers/slicedToArray'; import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { useToaster, resolveValue } from 'react-hot-toast'; import React__default from 'react'; import styled from 'styled-components'; import { PEEKS, MAX_TOASTS, MIN_TOAST_MOBILE, MIN_TOAST_DESKTOP, CONTAINER_GUTTER_MOBILE, CONTAINER_GUTTER_DESKTOP, GUTTER, SCALE_FACTOR, PEEK_GUTTER, TOAST_Z_INDEX, TOAST_MAX_WIDTH } from './constants.js'; import '../../utils/index.js'; import '../Box/BaseBox/index.js'; import { useIsMobile } from '../../utils/useIsMobile.js'; import '../../utils/metaAttribute/index.js'; import '../../utils/makeAnalyticsAttribute/index.js'; import { jsxs, jsx } from 'react/jsx-runtime'; import { BaseBox } from '../Box/BaseBox/BaseBox.web.js'; import { makeMotionTime } from '../../utils/makeMotionTime/makeMotionTime.web.js'; import useTheme from '../BladeProvider/useTheme.js'; import { makeSize } from '../../utils/makeSize/makeSize.js'; import { metaAttribute } from '../../utils/metaAttribute/metaAttribute.web.js'; import { MetaConstants } from '../../utils/metaAttribute/metaConstants.js'; import { makeAnalyticsAttribute } from '../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js'; var _excluded = ["reverseOrder", "position", "toastOptions", "containerClassName"]; 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 StyledToastWrapper = /*#__PURE__*/styled(BaseBox).withConfig({ displayName: "ToastContainerweb__StyledToastWrapper", componentId: "mv9fjf-0" })(function (_ref) { var isVisible = _ref.isVisible, index = _ref.index, isExpanded = _ref.isExpanded, isPromotional = _ref.isPromotional; var opacity = isVisible ? 1 : 0; // Only make the PEEKING and MAX_TOASTS toasts visible, // Every other toasts should be hidden if (index < PEEKS + MAX_TOASTS) { opacity = 1; } else if (isPromotional || isExpanded) { opacity = 1; } else { opacity = 0; } return { '& > *': { pointerEvents: opacity === 1 ? 'auto' : 'none' }, opacity: opacity }; }); var getPositionStyle = function getPositionStyle(position, offset, scale, theme) { var top = position.includes('top'); var verticalStyle = top ? { top: 0 } : { bottom: 0 }; var horizontalStyle = position.includes('center') ? { justifyContent: 'center' } : position.includes('right') ? { justifyContent: 'flex-end' } : {}; return _objectSpread(_objectSpread({ left: 0, right: 0, display: 'flex', position: 'absolute', transformOrigin: 'center', transition: "".concat(makeMotionTime(theme.motion.duration.gentle), " ").concat(theme.motion.easing.standard), transitionProperty: 'transform, opacity, height', transform: "translateY(".concat(offset * (top ? 1 : -1), "px) scale(").concat(scale, ")") }, verticalStyle), horizontalStyle); }; function isPromotionalToast(toast) { // @ts-expect-error return toast.type == 'promotional'; } var Toaster = function Toaster(_ref2) { var _promoToasts$, _promoToasts$0$height, _promoToasts$2; var reverseOrder = _ref2.reverseOrder, _ref2$position = _ref2.position, position = _ref2$position === void 0 ? 'top-center' : _ref2$position, toastOptions = _ref2.toastOptions, containerClassName = _ref2.containerClassName, rest = _objectWithoutProperties(_ref2, _excluded); var _useToaster = useToaster(toastOptions), toasts = _useToaster.toasts, handlers = _useToaster.handlers; var _useTheme = useTheme(), theme = _useTheme.theme; var _React$useState = React__default.useState(0), _React$useState2 = _slicedToArray(_React$useState, 2), frontToastHeight = _React$useState2[0], setFrontToastHeight = _React$useState2[1]; var _React$useState3 = React__default.useState(false), _React$useState4 = _slicedToArray(_React$useState3, 2), hasManuallyExpanded = _React$useState4[0], setHasManuallyExpanded = _React$useState4[1]; var isMobile = useIsMobile(); var minToasts = isMobile ? MIN_TOAST_MOBILE : MIN_TOAST_DESKTOP; var containerGutter = isMobile ? CONTAINER_GUTTER_MOBILE : CONTAINER_GUTTER_DESKTOP; var infoToasts = React__default.useMemo(function () { return toasts.filter(function (toast) { return !isPromotionalToast(toast); }); }, [toasts]); var promoToasts = React__default.useMemo(function () { return toasts.filter(function (toast) { return isPromotionalToast(toast); }); }, [toasts]); // always keep promo toasts at the bottom of the stack var recomputedToasts = React__default.useMemo(function () { return [].concat(_toConsumableArray(infoToasts), _toConsumableArray(promoToasts)); }, [infoToasts, promoToasts]); var hasPromoToast = promoToasts.length > 0 && ((_promoToasts$ = promoToasts[0]) === null || _promoToasts$ === void 0 ? void 0 : _promoToasts$.visible); var promoToastHeight = (_promoToasts$0$height = (_promoToasts$2 = promoToasts[0]) === null || _promoToasts$2 === void 0 ? void 0 : _promoToasts$2.height) !== null && _promoToasts$0$height !== void 0 ? _promoToasts$0$height : 0; var isExpanded = hasManuallyExpanded || recomputedToasts.length <= minToasts; React__default.useLayoutEffect(function () { // find the first toast which is visible var firstToast = infoToasts.find(function (t, index) { return t.visible && index === 0; }); if (firstToast) { var _firstToast$height; setFrontToastHeight((_firstToast$height = firstToast.height) !== null && _firstToast$height !== void 0 ? _firstToast$height : 0); } }, [infoToasts]); // calculate total height of all toasts var totalHeight = React__default.useMemo(function () { return recomputedToasts // only consider visible recomputedToasts .filter(function (toast) { return toast.visible; }).reduce(function (prevHeight, toast) { var _toast$height; return prevHeight + ((_toast$height = toast.height) !== null && _toast$height !== void 0 ? _toast$height : 0); }, 0) + recomputedToasts.length * GUTTER; }, [recomputedToasts]); // Stacking logic explained in detail: // https://www.loom.com/share/522d9a445e2f41e1886cce4decb9ab9d?sid=4287acf6-8d44-431b-93e1-c1a0d40a0aba // // 1. 3 toasts can be stacked on top of each other // 2. After 3 toasts, the toasts will be scaled down and peek from behind // 3. There can be maximum of 3 toasts peeking from behind // 4. After 3 peeking toasts, the toasts will be hidden // 5. If there is a promo toast, all toasts will be lifted up // 6. Promo toasts will always be on the bottom var calculateYPosition = React__default.useCallback(function (_ref3) { var toast = _ref3.toast, index = _ref3.index; // find the current toast index var toastIndex = infoToasts.findIndex(function (t) { return t.id === toast.id; }); // number of toasts before this toast var toastsBefore = infoToasts.filter(function (toast, i) { return i < toastIndex && toast.visible; }).length; var scale = Math.max(0.7, 1 - toastsBefore * SCALE_FACTOR); // first toast should always have a scale of 1 if (index < MAX_TOASTS) { scale = 1; } // y position of toast, var offset = infoToasts.filter(function (toast) { return toast.visible; }).slice(0, toastsBefore).reduce(function (y, toast) { // if the toast is expanded, add the height of the toast + gutter if (isExpanded) { var _toast$height2; return y + ((_toast$height2 = toast.height) !== null && _toast$height2 !== void 0 ? _toast$height2 : 0) + GUTTER; } // if the toast is not expanded, add only the peek gutter return y + PEEK_GUTTER; }, 0); // lift all info toasts up if there is a promo toast if (hasPromoToast) { offset += GUTTER + promoToastHeight; } // if this is a promo toast, then put it at the bottom and force the scale to 1 if (isPromotionalToast(toast)) { offset = 0; scale = 1; } return { offset: offset, scale: isExpanded ? 1 : scale }; }, [hasPromoToast, infoToasts, isExpanded, promoToastHeight]); var handleMouseEnter = function handleMouseEnter() { if (isMobile) return; setHasManuallyExpanded(true); handlers.startPause(); }; var handleMouseLeave = function handleMouseLeave() { if (isMobile) return; setHasManuallyExpanded(false); handlers.endPause(); }; var handleToastClick = function handleToastClick() { if (!isMobile) return; setHasManuallyExpanded(function (prev) { var next = !prev; if (next) { handlers.startPause(); } else { handlers.endPause(); } return next; }); }; return /*#__PURE__*/jsxs(BaseBox, _objectSpread(_objectSpread(_objectSpread({ position: "fixed", zIndex: TOAST_Z_INDEX, top: makeSize(containerGutter), left: makeSize(containerGutter), right: makeSize(containerGutter), bottom: makeSize(containerGutter), width: "calc(100% - ".concat(containerGutter * 2, "px)"), maxWidth: makeSize(TOAST_MAX_WIDTH), pointerEvents: "none", className: containerClassName }, metaAttribute({ name: MetaConstants.ToastContainer })), makeAnalyticsAttribute(rest)), {}, { children: [/*#__PURE__*/jsx(BaseBox, _objectSpread({ position: "absolute", bottom: "".concat(promoToastHeight, "px"), left: "0px", width: "100%", pointerEvents: isExpanded ? 'all' : 'none', height: makeSize(isExpanded ? totalHeight - promoToastHeight : frontToastHeight), zIndex: -100, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onClick: handleToastClick }, metaAttribute({ testID: 'toast-mouseover-container' }))), recomputedToasts.map(function (toast, index) { var _toast$position; var toastPosition = (_toast$position = toast.position) !== null && _toast$position !== void 0 ? _toast$position : position; var isPromotional = isPromotionalToast(toast); var _calculateYPosition = calculateYPosition({ toast: toast, isExpanded: isExpanded, reverseOrder: reverseOrder, index: index }), offset = _calculateYPosition.offset, scale = _calculateYPosition.scale; var positionStyle = getPositionStyle(toastPosition, offset, scale, theme); // recalculate height of toast var ref = function ref(el) { if (el && typeof toast.height !== 'number') { var height = el.getBoundingClientRect().height; handlers.updateHeight(toast.id, height); } }; var toastHeight = toast.height; if (index > MAX_TOASTS - 1 && !isPromotional) { toastHeight = frontToastHeight; } if (isExpanded) { toastHeight = toast.height; } return /*#__PURE__*/jsx(StyledToastWrapper, { index: index, ref: ref, isExpanded: isExpanded, isVisible: toast.visible, isPromotional: isPromotional, style: _objectSpread(_objectSpread({}, positionStyle), {}, { zIndex: -1 * index, height: toastHeight, overflow: 'hidden' }), onMouseEnter: function onMouseEnter() { if (isPromotional) return; handleMouseEnter(); }, onMouseLeave: function onMouseLeave() { if (isPromotional) return; handleMouseLeave(); }, onClick: function onClick() { if (isPromotional) return; handleToastClick(); }, children: /*#__PURE__*/jsx(BaseBox, { height: "fit-content", width: "100%", children: resolveValue(toast.message, _objectSpread(_objectSpread({}, toast), {}, { index: index })) }) }, toast.id); })] })); }; var ToastContainer = function ToastContainer() { return /*#__PURE__*/jsx(Toaster, { position: "bottom-left" }); }; export { ToastContainer }; //# sourceMappingURL=ToastContainer.web.js.map