@razorpay/blade
Version:
The Design System that powers Razorpay
488 lines (484 loc) • 21.3 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
import React__default from 'react';
import styled from 'styled-components';
import StyledBaseButton from './StyledBaseButton.web.js';
import { backgroundColor, textColor, buttonIconOnlySizeToIconSizeMap, buttonSizeToIconSizeMap, buttonSizeToSpinnerSizeMap, minHeight, buttonIconOnlyHeightWidth, buttonIconPadding, buttonPadding, typography } from './buttonTokens.js';
import AnimatedButtonContent from './AnimatedButtonContent.web.js';
import getIn from '../../../utils/lodashButBetter/get.js';
import '../../../utils/index.js';
import { useButtonGroupContext } from '../../ButtonGroup/ButtonGroupContext.js';
import '../../Box/styledProps/index.js';
import '../../Typography/BaseText/index.js';
import '../../BladeProvider/index.js';
import '../../LiveAnnouncer/index.web.js';
import '../../Spinner/BaseSpinner/index.js';
import '../../Box/BaseBox/index.js';
import '../../../utils/assignWithoutSideEffects/index.js';
import '../../../utils/usePrevious/index.js';
import '../../../utils/makeSize/index.js';
import '../../../utils/makeBorderSize/index.js';
import '../../../utils/makeAccessible/index.js';
import '../../../utils/makeSpace/index.js';
import '../../../utils/metaAttribute/index.js';
import '../../../utils/getStringChildren/index.js';
import '../../../utils/logger/index.js';
import '../../../utils/makeAnalyticsAttribute/index.js';
import { jsx, jsxs } from 'react/jsx-runtime';
import { isReactNative } from '../../../utils/platform/isReactNative.js';
import { throwBladeError } from '../../../utils/logger/logger.js';
import { makeSize } from '../../../utils/makeSize/makeSize.js';
import { makeSpace } from '../../../utils/makeSpace/makeSpace.js';
import { makeBorderSize } from '../../../utils/makeBorderSize/makeBorderSize.js';
import { BaseBox } from '../../Box/BaseBox/BaseBox.web.js';
import useTheme from '../../BladeProvider/useTheme.js';
import { getStringFromReactText } from '../../../utils/getStringChildren/getStringChildren.js';
import { usePrevious } from '../../../utils/usePrevious/usePrevious.js';
import { announce } from '../../LiveAnnouncer/LiveAnnouncer.web.js';
import { makeAccessible } from '../../../utils/makeAccessible/makeAccessible.web.js';
import { metaAttribute } from '../../../utils/metaAttribute/metaAttribute.web.js';
import { MetaConstants } from '../../../utils/metaAttribute/metaConstants.js';
import { getStyledProps } from '../../Box/styledProps/getStyledProps.js';
import { makeAnalyticsAttribute } from '../../../utils/makeAnalyticsAttribute/makeAnalyticsAttribute.js';
import { BaseSpinner } from '../../Spinner/BaseSpinner/BaseSpinner.js';
import { BaseText } from '../../Typography/BaseText/BaseText.web.js';
import { assignWithoutSideEffects } from '../../../utils/assignWithoutSideEffects/assignWithoutSideEffects.js';
var _excluded = ["href", "target", "rel", "tabIndex", "id", "variant", "color", "size", "icon", "iconPosition", "isDisabled", "isFullWidth", "isLoading", "onClick", "onBlur", "onKeyDown", "type", "children", "testID", "onFocus", "onMouseLeave", "onMouseMove", "onMouseDown", "onPointerDown", "onPointerEnter", "accessibilityProps", "onTouchEnd", "onTouchStart"];
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 getRenderElement = function getRenderElement(href) {
if (isReactNative()) {
return undefined; // as property doesn't work with react native
}
if (href) {
return 'a';
}
return 'button';
};
var getBackgroundColorToken = function getBackgroundColorToken(_ref) {
var property = _ref.property,
variant = _ref.variant,
state = _ref.state,
color = _ref.color;
var _state = state === 'focus' || state === 'hover' ? 'highlighted' : state;
var tokens = backgroundColor(property);
if (color === 'white') {
return tokens.white[variant][_state];
}
if (color && color !== 'primary') {
if (variant === 'tertiary') {
throw new Error("Tertiary variant can only be used with color: \"primary\" or \"white\" but received \"".concat(color, "\""));
}
return tokens.color(color)[variant][_state];
}
return tokens.base[variant][_state];
};
var getTextColorToken = function getTextColorToken(_ref2) {
var property = _ref2.property,
variant = _ref2.variant,
state = _ref2.state,
color = _ref2.color;
var tokens = textColor(property);
var _state = state === 'focus' || state === 'hover' ? 'highlighted' : state;
if (color === 'white') {
return tokens.white[variant][_state];
}
if (color && color !== 'primary') {
if (variant === 'tertiary') {
throw new Error("Tertiary variant can only be used with color: \"primary\" or \"white\" but received \"".concat(color, "\""));
}
return tokens.color(color)[variant][_state];
}
return tokens.base[variant][_state];
};
var getProps = function getProps(_ref3) {
var buttonTypographyTokens = _ref3.buttonTypographyTokens,
childrenString = _ref3.childrenString,
isDisabled = _ref3.isDisabled,
size = _ref3.size,
theme = _ref3.theme,
variant = _ref3.variant,
color = _ref3.color,
hasIcon = _ref3.hasIcon;
if (variant === 'tertiary' && color !== 'primary' && color !== 'white') {
throwBladeError({
moduleName: 'BaseButton',
message: "Tertiary variant can only be used with color: \"primary\" or \"white\" but received \"".concat(color, "\"")
});
}
var isIconOnly = hasIcon && (!childrenString || (childrenString === null || childrenString === void 0 ? void 0 : childrenString.trim().length) === 0);
var props = {
iconSize: isIconOnly ? buttonIconOnlySizeToIconSizeMap[size] : buttonSizeToIconSizeMap[size],
spinnerSize: buttonSizeToSpinnerSizeMap[size],
fontSize: buttonTypographyTokens.fonts.size[size],
lineHeight: buttonTypographyTokens.lineHeights[size],
minHeight: makeSize(minHeight[size]),
height: isIconOnly ? buttonIconOnlyHeightWidth[size] : undefined,
width: isIconOnly ? buttonIconOnlyHeightWidth[size] : undefined,
iconPadding: hasIcon && childrenString !== null && childrenString !== void 0 && childrenString.trim() ? "spacing.".concat(buttonIconPadding[size]) : undefined,
iconColor: getTextColorToken({
property: 'icon',
variant: variant,
color: color,
state: 'default'
}),
textColor: getTextColorToken({
property: 'text',
variant: variant,
color: color,
state: 'default'
}),
buttonPaddingTop: isIconOnly ? makeSpace(0) : makeSpace(theme.spacing[buttonPadding[size].top]),
buttonPaddingBottom: isIconOnly ? makeSpace(0) : makeSpace(theme.spacing[buttonPadding[size].bottom]),
buttonPaddingLeft: isIconOnly ? makeSpace(0) : makeSpace(theme.spacing[buttonPadding[size].left]),
buttonPaddingRight: isIconOnly ? makeSpace(0) : makeSpace(theme.spacing[buttonPadding[size].right]),
text: childrenString === null || childrenString === void 0 ? void 0 : childrenString.trim(),
defaultBackgroundColor: getIn(theme.colors, getBackgroundColorToken({
property: 'background',
variant: variant,
color: color,
state: 'default'
})),
defaultBorderColor: getIn(theme.colors, getBackgroundColorToken({
property: 'border',
variant: variant,
color: color,
state: 'default'
})),
hoverBackgroundColor: getIn(theme.colors, getBackgroundColorToken({
property: 'background',
variant: variant,
color: color,
state: 'hover'
})),
hoverBorderColor: getIn(theme.colors, getBackgroundColorToken({
property: 'border',
variant: variant,
color: color,
state: 'hover'
})),
focusBackgroundColor: getIn(theme.colors, getBackgroundColorToken({
property: 'background',
variant: variant,
color: color,
state: 'focus'
})),
focusBorderColor: getIn(theme.colors, getBackgroundColorToken({
property: 'border',
variant: variant,
color: color,
state: 'focus'
})),
focusRingColor: getIn(theme.colors, 'surface.border.primary.muted'),
borderWidth: variant == 'secondary' ? makeBorderSize(theme.border.width.thin) : '0px',
borderRadius: makeBorderSize(theme.border.radius.medium),
motionDuration: 'duration.xquick',
motionEasing: 'easing.standard'
};
if (isDisabled) {
var disabledBackgroundColor = getIn(theme.colors, getBackgroundColorToken({
property: 'background',
variant: variant,
color: color,
state: 'disabled'
}));
var disabledBorderColor = getIn(theme.colors, getBackgroundColorToken({
property: 'border',
variant: variant,
color: color,
state: 'disabled'
}));
props.iconColor = getTextColorToken({
property: 'icon',
variant: variant,
color: color,
state: 'disabled'
});
props.textColor = getTextColorToken({
property: 'text',
variant: variant,
color: color,
state: 'disabled'
});
props.defaultBackgroundColor = disabledBackgroundColor;
props.defaultBorderColor = disabledBorderColor;
props.hoverBackgroundColor = disabledBackgroundColor;
props.hoverBorderColor = disabledBorderColor;
props.focusBackgroundColor = disabledBackgroundColor;
props.focusBorderColor = disabledBorderColor;
}
return props;
};
var ButtonContent = /*#__PURE__*/styled(BaseBox).withConfig({
displayName: "BaseButton__ButtonContent",
componentId: "zf1huq-0"
})(function (_ref4) {
var isHidden = _ref4.isHidden;
return {
opacity: isHidden ? 0 : 1
};
});
var _BaseButton = function _BaseButton(_ref5, ref) {
var _buttonGroupProps$siz, _buttonGroupProps$var, _buttonGroupProps$col, _accessibilityProps$r, _buttonGroupProps$isF;
var href = _ref5.href,
target = _ref5.target,
rel = _ref5.rel,
tabIndex = _ref5.tabIndex,
id = _ref5.id,
_ref5$variant = _ref5.variant,
variant = _ref5$variant === void 0 ? 'primary' : _ref5$variant,
_ref5$color = _ref5.color,
color = _ref5$color === void 0 ? 'primary' : _ref5$color,
_ref5$size = _ref5.size,
size = _ref5$size === void 0 ? 'medium' : _ref5$size,
Icon = _ref5.icon,
_ref5$iconPosition = _ref5.iconPosition,
iconPosition = _ref5$iconPosition === void 0 ? 'left' : _ref5$iconPosition,
_ref5$isDisabled = _ref5.isDisabled,
isDisabled = _ref5$isDisabled === void 0 ? false : _ref5$isDisabled,
_ref5$isFullWidth = _ref5.isFullWidth,
isFullWidth = _ref5$isFullWidth === void 0 ? false : _ref5$isFullWidth,
_ref5$isLoading = _ref5.isLoading,
isLoading = _ref5$isLoading === void 0 ? false : _ref5$isLoading,
onClick = _ref5.onClick,
onBlur = _ref5.onBlur,
_onKeyDown = _ref5.onKeyDown,
_ref5$type = _ref5.type,
type = _ref5$type === void 0 ? 'button' : _ref5$type,
children = _ref5.children,
testID = _ref5.testID,
onFocus = _ref5.onFocus,
onMouseLeave = _ref5.onMouseLeave,
onMouseMove = _ref5.onMouseMove,
_onMouseDown = _ref5.onMouseDown,
onPointerDown = _ref5.onPointerDown,
onPointerEnter = _ref5.onPointerEnter,
accessibilityProps = _ref5.accessibilityProps,
_onTouchEnd = _ref5.onTouchEnd,
_onTouchStart = _ref5.onTouchStart,
rest = _objectWithoutProperties(_ref5, _excluded);
var _useTheme = useTheme(),
theme = _useTheme.theme;
var buttonGroupProps = useButtonGroupContext();
var _React$useState = React__default.useState(false),
_React$useState2 = _slicedToArray(_React$useState, 2),
isPressed = _React$useState2[0],
setIsPressed = _React$useState2[1];
var isLink = Boolean(href);
var childrenString = getStringFromReactText(children);
var isChildrenComponent = /*#__PURE__*/React__default.isValidElement(children);
// Button cannot be disabled when its rendered as Link
// button should be allowed to be disabled in any case...
// either through button group or we should allow to disable an individual button
var disabled = buttonGroupProps.isDisabled || isLoading || isDisabled && !isLink;
if (false) {
if (!Icon && !(childrenString !== null && childrenString !== void 0 && childrenString.trim())) {
throwBladeError({
message: 'At least one of icon or text is required to render a button.',
moduleName: 'BaseButton'
});
}
}
var prevLoading = usePrevious(isLoading);
React__default.useEffect(function () {
if (isLoading) announce('Started loading');
if (!isLoading && prevLoading) announce('Stopped loading');
}, [isLoading, prevLoading]);
var _getProps = getProps({
buttonTypographyTokens: typography,
childrenString: childrenString,
isDisabled: disabled,
size: (_buttonGroupProps$siz = buttonGroupProps.size) !== null && _buttonGroupProps$siz !== void 0 ? _buttonGroupProps$siz : size,
variant: (_buttonGroupProps$var = buttonGroupProps.variant) !== null && _buttonGroupProps$var !== void 0 ? _buttonGroupProps$var : variant,
theme: theme,
color: (_buttonGroupProps$col = buttonGroupProps.color) !== null && _buttonGroupProps$col !== void 0 ? _buttonGroupProps$col : color,
hasIcon: Boolean(Icon)
}),
defaultBorderColor = _getProps.defaultBorderColor,
defaultBackgroundColor = _getProps.defaultBackgroundColor,
minHeight = _getProps.minHeight,
height = _getProps.height,
width = _getProps.width,
buttonPaddingTop = _getProps.buttonPaddingTop,
buttonPaddingBottom = _getProps.buttonPaddingBottom,
buttonPaddingLeft = _getProps.buttonPaddingLeft,
buttonPaddingRight = _getProps.buttonPaddingRight,
focusBorderColor = _getProps.focusBorderColor,
focusBackgroundColor = _getProps.focusBackgroundColor,
focusRingColor = _getProps.focusRingColor,
fontSize = _getProps.fontSize,
hoverBorderColor = _getProps.hoverBorderColor,
hoverBackgroundColor = _getProps.hoverBackgroundColor,
iconColor = _getProps.iconColor,
iconSize = _getProps.iconSize,
iconPadding = _getProps.iconPadding,
spinnerSize = _getProps.spinnerSize,
lineHeight = _getProps.lineHeight,
text = _getProps.text,
textColor = _getProps.textColor,
borderWidth = _getProps.borderWidth,
borderRadius = _getProps.borderRadius,
motionDuration = _getProps.motionDuration,
motionEasing = _getProps.motionEasing;
var renderElement = React__default.useMemo(function () {
return getRenderElement(href);
}, [href]);
var defaultRole = isLink ? 'link' : 'button';
var handlePointerPressedIn = React__default.useCallback(function () {
if (disabled) return;
setIsPressed(true);
}, [disabled]);
var handlePointerPressedOut = React__default.useCallback(function () {
if (disabled) return;
setIsPressed(false);
}, [disabled]);
var handleKeyboardPressedIn = React__default.useCallback(function (e) {
if (disabled) return;
if (e.key === ' ' || e.key === 'Enter') {
setIsPressed(true);
}
}, [disabled]);
var handleKeyboardPressedOut = React__default.useCallback(function (e) {
if (disabled) return;
if (e.key === ' ' || e.key === 'Enter') {
setIsPressed(false);
}
}, [disabled]);
return /*#__PURE__*/jsx(StyledBaseButton, _objectSpread(_objectSpread(_objectSpread(_objectSpread({
ref: ref,
id: id
// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error
// @ts-ignore: On React Native it will always be undefined but TS doesn't understand that
,
as: renderElement,
href: href,
target: target,
rel: rel,
accessibilityProps: _objectSpread({}, makeAccessible(_objectSpread(_objectSpread({}, accessibilityProps), {}, {
role: (_accessibilityProps$r = accessibilityProps === null || accessibilityProps === void 0 ? void 0 : accessibilityProps.role) !== null && _accessibilityProps$r !== void 0 ? _accessibilityProps$r : defaultRole
}))),
variant: variant,
isLoading: isLoading,
disabled: disabled,
defaultBorderColor: defaultBorderColor,
minHeight: minHeight,
buttonPaddingTop: buttonPaddingTop,
buttonPaddingBottom: buttonPaddingBottom,
buttonPaddingLeft: buttonPaddingLeft,
buttonPaddingRight: buttonPaddingRight,
defaultBackgroundColor: defaultBackgroundColor,
focusBorderColor: focusBorderColor,
focusBackgroundColor: focusBackgroundColor,
focusRingColor: focusRingColor,
hoverBorderColor: hoverBorderColor,
hoverBackgroundColor: hoverBackgroundColor,
isFullWidth: (_buttonGroupProps$isF = buttonGroupProps.isFullWidth) !== null && _buttonGroupProps$isF !== void 0 ? _buttonGroupProps$isF : isFullWidth,
onClick: onClick,
onBlur: onBlur,
onFocus: onFocus,
onMouseLeave: onMouseLeave,
onMouseMove: onMouseMove,
tabIndex: tabIndex,
onPointerDown: onPointerDown,
onPointerEnter: onPointerEnter
// Setting type for web fails it on native typecheck and vice versa
,
onKeyDown: function onKeyDown(event) {
handleKeyboardPressedIn(event);
_onKeyDown === null || _onKeyDown === void 0 ? void 0 : _onKeyDown(event);
},
onTouchStart: function onTouchStart(event) {
handlePointerPressedIn();
_onTouchStart === null || _onTouchStart === void 0 ? void 0 : _onTouchStart(event);
},
onTouchEnd: function onTouchEnd(event) {
handlePointerPressedOut();
_onTouchEnd === null || _onTouchEnd === void 0 ? void 0 : _onTouchEnd(event);
},
type: type,
borderWidth: borderWidth,
borderRadius: borderRadius,
motionDuration: motionDuration,
motionEasing: motionEasing,
height: height,
width: width,
isPressed: isPressed,
onMouseDown: function onMouseDown(event) {
handlePointerPressedIn();
_onMouseDown === null || _onMouseDown === void 0 ? void 0 : _onMouseDown(event);
},
onMouseUp: handlePointerPressedOut,
onMouseOut: handlePointerPressedOut,
onKeyUp: handleKeyboardPressedOut
}, metaAttribute({
name: MetaConstants.Button,
testID: testID
})), getStyledProps(rest)), makeAnalyticsAttribute(rest)), {}, {
children: /*#__PURE__*/jsxs(AnimatedButtonContent, {
motionDuration: motionDuration,
motionEasing: motionEasing,
isPressed: isPressed,
children: [isLoading ? /*#__PURE__*/jsx(BaseBox, {
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "absolute",
top: "0px",
left: "0px",
bottom: "0px",
right: "0px",
zIndex: 1,
children: /*#__PURE__*/jsx(BaseSpinner, {
accessibilityLabel: "Loading",
size: spinnerSize,
color: color
})
}) : null, /*#__PURE__*/jsxs(ButtonContent, {
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
flex: 1,
isHidden: isLoading,
zIndex: 1,
children: [Icon && iconPosition == 'left' ? /*#__PURE__*/jsx(BaseBox, {
paddingRight: iconPadding,
display: "flex",
justifyContent: "center",
alignItems: "center",
children: /*#__PURE__*/jsx(Icon, {
size: iconSize,
color: iconColor
})
}) : null, text ? isChildrenComponent ? children : /*#__PURE__*/jsx(BaseText, {
lineHeight: lineHeight,
fontSize: fontSize
// figma and web have different font-smoothing properties
// which causes web version of button text to look much bolder
// than figma version. To fix this we are changing font-weight from 600 to 500
// https://forum.figma.com/t/why-does-a-font-weight-in-figma-seem-lighter-than-the-same-weight-in-the-browser/2207
,
fontWeight: "medium",
textAlign: "center",
color: textColor,
children: text
}) : null, Icon && iconPosition == 'right' ? /*#__PURE__*/jsx(BaseBox, {
paddingLeft: iconPadding,
display: "flex",
justifyContent: "center",
alignItems: "center",
children: /*#__PURE__*/jsx(Icon, {
size: iconSize,
color: iconColor
})
}) : null]
})]
})
}));
};
var BaseButton = /*#__PURE__*/assignWithoutSideEffects( /*#__PURE__*/React__default.forwardRef(_BaseButton), {
displayName: 'BaseButton'
});
export { BaseButton as default, getBackgroundColorToken, getTextColorToken };
//# sourceMappingURL=BaseButton.js.map