@razorpay/blade
Version:
The Design System that powers Razorpay
315 lines (306 loc) • 12.9 kB
JavaScript
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