UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

525 lines (519 loc) 23.5 kB
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 { backgroundGradient, boxShadow, textColor, buttonIconOnlySizeToIconSizeMap, buttonSizeToIconSizeMap, buttonSizeToSpinnerSizeMap, minHeight, buttonIconOnlyHeightWidth, buttonPadding, buttonBorderRadius, typography, spinnerColor } 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 variant = _ref.variant, state = _ref.state, color = _ref.color; var _state = state === 'focus' || state === 'hover' ? 'highlighted' : state; // For white and transparent colors, we use 'primary' as the base color // since the actual token lookup uses the white/transparent specific paths var gradientColor = color === 'white' || color === 'transparent' || !color ? 'primary' : color; var tokens = backgroundGradient(gradientColor); if (color === 'white') { return tokens.white[variant][_state]; } if (color === 'transparent') { if (variant !== 'tertiary') { throw new Error("Transparent color can only be used with tertiary variant but received \"".concat(variant, "\"")); } return tokens.base.transparent[_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.base[variant][_state]; }; var getBoxShadowToken = function getBoxShadowToken(_ref2) { var variant = _ref2.variant, state = _ref2.state, color = _ref2.color; var _state = state === 'focus' || state === 'hover' ? 'highlighted' : state; var tokenColor = color === 'white' || color === 'transparent' || !color ? 'primary' : color; var tokens = boxShadow(tokenColor); if (color === 'white') { return tokens.white[variant][_state]; } if (color === 'transparent') { if (variant !== 'tertiary') { throw new Error("Transparent color can only be used with tertiary variant but received \"".concat(variant, "\"")); } return tokens.base.transparent[_state]; } return tokens.base[variant][_state]; }; var getTextColorToken = function getTextColorToken(_ref3) { var property = _ref3.property, variant = _ref3.variant, state = _ref3.state, color = _ref3.color; var tokens = textColor(property); var _state = state === 'focus' || state === 'hover' ? 'highlighted' : state; if (color === 'white') { return tokens.white[variant][_state]; } if (color === 'transparent') { if (variant !== 'tertiary') { throw new Error("Transparent color can only be used with tertiary variant but received \"".concat(variant, "\"")); } return tokens.transparent.tertiary[_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(_ref4) { var buttonTypographyTokens = _ref4.buttonTypographyTokens, childrenString = _ref4.childrenString, isDisabled = _ref4.isDisabled, size = _ref4.size, theme = _ref4.theme, variant = _ref4.variant, color = _ref4.color, hasIcon = _ref4.hasIcon; if (variant === 'tertiary' && color !== 'primary' && color !== 'white' && color !== 'transparent') { throwBladeError({ moduleName: 'BaseButton', message: "Tertiary variant can only be used with color: \"primary\" or \"white\" or \"transparent\" but received \"".concat(color, "\"") }); } var isIconOnly = hasIcon && (!childrenString || (childrenString === null || childrenString === void 0 ? void 0 : childrenString.trim().length) === 0); // Resolve background value - either a gradient string or a token path var resolveBackgroundValue = function resolveBackgroundValue(value) { if (value.startsWith('linear-gradient')) { return value; } return getIn(theme.colors, value); }; var getDefaultBackground = function getDefaultBackground() { return resolveBackgroundValue(getBackgroundColorToken({ variant: variant, color: color, state: 'default' })); }; var getBoxShadow = function getBoxShadow(state, btnColor) { var shadowTokens = getBoxShadowToken({ variant: variant, color: btnColor, state: state }); if (shadowTokens.length === 0) { return undefined; } var shadows = shadowTokens.map(function (shadow) { var resolvedColor = getIn(theme.colors, shadow.color); return "inset 0 ".concat(shadow.y, "px ").concat(shadow.blur, "px ").concat(shadow.spread, "px ").concat(resolvedColor); }); return shadows.join(', '); }; 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, 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: getDefaultBackground(), defaultBoxShadow: getBoxShadow('default', color), hoverBackgroundColor: resolveBackgroundValue(getBackgroundColorToken({ variant: variant, color: color, state: 'hover' })), hoverBoxShadow: getBoxShadow('hover', color), hoverIconColor: getIn(theme.colors, getTextColorToken({ property: 'icon', variant: variant, color: color, state: 'hover' })), focusBackgroundColor: resolveBackgroundValue(getBackgroundColorToken({ variant: variant, color: color, state: 'focus' })), focusBoxShadow: getBoxShadow('focus', color), focusRingColor: getIn(theme.colors, 'surface.border.primary.muted'), borderRadius: makeBorderSize(theme.border.radius[buttonBorderRadius[size]]), motionDuration: 'duration.xquick', motionEasing: 'easing.standard' }; if (isDisabled) { var disabledBackgroundColor = resolveBackgroundValue(getBackgroundColorToken({ 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.defaultBoxShadow = getBoxShadow('disabled', color); props.hoverBackgroundColor = disabledBackgroundColor; props.hoverBoxShadow = getBoxShadow('disabled', color); props.focusBackgroundColor = disabledBackgroundColor; props.focusBoxShadow = getBoxShadow('disabled', color); } return props; }; var ButtonContent = /*#__PURE__*/styled(BaseBox).withConfig({ displayName: "BaseButton__ButtonContent", componentId: "zf1huq-0" })(function (_ref5) { var isHidden = _ref5.isHidden; return { opacity: isHidden ? 0 : 1 }; }); var _BaseButton = function _BaseButton(_ref6, ref) { var _buttonGroupProps$siz, _buttonGroupProps$var, _buttonGroupProps$col, _accessibilityProps$r, _buttonGroupProps$var2, _buttonGroupProps$col2, _buttonGroupProps$siz2, _buttonGroupProps$isF; var href = _ref6.href, target = _ref6.target, rel = _ref6.rel, tabIndex = _ref6.tabIndex, id = _ref6.id, _ref6$variant = _ref6.variant, variant = _ref6$variant === void 0 ? 'primary' : _ref6$variant, _ref6$color = _ref6.color, color = _ref6$color === void 0 ? 'primary' : _ref6$color, _ref6$size = _ref6.size, size = _ref6$size === void 0 ? 'medium' : _ref6$size, Icon = _ref6.icon, _ref6$iconPosition = _ref6.iconPosition, iconPosition = _ref6$iconPosition === void 0 ? 'left' : _ref6$iconPosition, _ref6$isDisabled = _ref6.isDisabled, isDisabled = _ref6$isDisabled === void 0 ? false : _ref6$isDisabled, _ref6$isFullWidth = _ref6.isFullWidth, isFullWidth = _ref6$isFullWidth === void 0 ? false : _ref6$isFullWidth, _ref6$isLoading = _ref6.isLoading, isLoading = _ref6$isLoading === void 0 ? false : _ref6$isLoading, onClick = _ref6.onClick, onBlur = _ref6.onBlur, _onKeyDown = _ref6.onKeyDown, _ref6$type = _ref6.type, type = _ref6$type === void 0 ? 'button' : _ref6$type, children = _ref6.children, testID = _ref6.testID, onFocus = _ref6.onFocus, onMouseLeave = _ref6.onMouseLeave, onMouseMove = _ref6.onMouseMove, _onMouseDown = _ref6.onMouseDown, onPointerDown = _ref6.onPointerDown, onPointerEnter = _ref6.onPointerEnter, accessibilityProps = _ref6.accessibilityProps, _onTouchEnd = _ref6.onTouchEnd, _onTouchStart = _ref6.onTouchStart, rest = _objectWithoutProperties(_ref6, _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) }), defaultBackgroundColor = _getProps.defaultBackgroundColor, defaultBoxShadow = _getProps.defaultBoxShadow, minHeight = _getProps.minHeight, height = _getProps.height, width = _getProps.width, buttonPaddingTop = _getProps.buttonPaddingTop, buttonPaddingBottom = _getProps.buttonPaddingBottom, buttonPaddingLeft = _getProps.buttonPaddingLeft, buttonPaddingRight = _getProps.buttonPaddingRight, focusBoxShadow = _getProps.focusBoxShadow, focusBackgroundColor = _getProps.focusBackgroundColor, focusRingColor = _getProps.focusRingColor, fontSize = _getProps.fontSize, hoverBoxShadow = _getProps.hoverBoxShadow, hoverBackgroundColor = _getProps.hoverBackgroundColor, hoverIconColor = _getProps.hoverIconColor, iconColor = _getProps.iconColor, iconSize = _getProps.iconSize, spinnerSize = _getProps.spinnerSize, lineHeight = _getProps.lineHeight, text = _getProps.text, textColor = _getProps.textColor, 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: (_buttonGroupProps$var2 = buttonGroupProps.variant) !== null && _buttonGroupProps$var2 !== void 0 ? _buttonGroupProps$var2 : variant, color: (_buttonGroupProps$col2 = buttonGroupProps.color) !== null && _buttonGroupProps$col2 !== void 0 ? _buttonGroupProps$col2 : color, size: (_buttonGroupProps$siz2 = buttonGroupProps.size) !== null && _buttonGroupProps$siz2 !== void 0 ? _buttonGroupProps$siz2 : size, isLoading: isLoading, disabled: disabled, minHeight: minHeight, buttonPaddingTop: buttonPaddingTop, buttonPaddingBottom: buttonPaddingBottom, buttonPaddingLeft: buttonPaddingLeft, buttonPaddingRight: buttonPaddingRight, defaultBackgroundColor: defaultBackgroundColor, defaultBoxShadow: defaultBoxShadow, focusBoxShadow: focusBoxShadow, focusBackgroundColor: focusBackgroundColor, focusRingColor: focusRingColor, hoverBoxShadow: hoverBoxShadow, 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 || _onKeyDown(event); }, onTouchStart: function onTouchStart(event) { handlePointerPressedIn(); _onTouchStart === null || _onTouchStart === void 0 || _onTouchStart(event); }, onTouchEnd: function onTouchEnd(event) { handlePointerPressedOut(); _onTouchEnd === null || _onTouchEnd === void 0 || _onTouchEnd(event); }, type: type, borderRadius: borderRadius, motionDuration: motionDuration, motionEasing: motionEasing, height: height, width: width, isPressed: isPressed, hoverIconColor: isDisabled ? getIn(theme.colors, iconColor) : hoverIconColor, onMouseDown: function onMouseDown(event) { handlePointerPressedIn(); _onMouseDown === null || _onMouseDown === 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 && color !== 'primary' && color !== 'transparent' && color in spinnerColor ? spinnerColor[color][variant] : spinnerColor.base[variant] }) }) : 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, { display: "flex", justifyContent: "center", alignItems: "center", children: /*#__PURE__*/jsx(Icon, { size: iconSize, color: iconColor }) }) : null, text ? isChildrenComponent ? children : /*#__PURE__*/jsx(BaseText, { marginX: "spacing.2", 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, { 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, getBoxShadowToken, getTextColorToken }; //# sourceMappingURL=BaseButton.js.map