button.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. "use client";
  2. var __rest = this && this.__rest || function (s, e) {
  3. var t = {};
  4. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
  5. if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
  6. if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
  7. }
  8. return t;
  9. };
  10. import React, { Children, useContext, useEffect, useMemo, useRef, useState } from 'react';
  11. import classNames from 'classnames';
  12. import omit from "rc-util/es/omit";
  13. import { useComposeRef } from "rc-util/es/ref";
  14. import useLayoutEffect from "rc-util/es/hooks/useLayoutEffect";
  15. import { devUseWarning } from '../_util/warning';
  16. import Wave from '../_util/wave';
  17. import { ConfigContext, useComponentConfig } from '../config-provider/context';
  18. import DisabledContext from '../config-provider/DisabledContext';
  19. import useSize from '../config-provider/hooks/useSize';
  20. import { useCompactItemContext } from '../space/Compact';
  21. import Group, { GroupSizeContext } from './button-group';
  22. import { isTwoCNChar, isUnBorderedButtonVariant, spaceChildren } from './buttonHelpers';
  23. import DefaultLoadingIcon from './DefaultLoadingIcon';
  24. import IconWrapper from './IconWrapper';
  25. import useStyle from './style';
  26. import Compact from './style/compact';
  27. function getLoadingConfig(loading) {
  28. if (typeof loading === 'object' && loading) {
  29. let delay = loading === null || loading === void 0 ? void 0 : loading.delay;
  30. delay = !Number.isNaN(delay) && typeof delay === 'number' ? delay : 0;
  31. return {
  32. loading: delay <= 0,
  33. delay
  34. };
  35. }
  36. return {
  37. loading: !!loading,
  38. delay: 0
  39. };
  40. }
  41. const ButtonTypeMap = {
  42. default: ['default', 'outlined'],
  43. primary: ['primary', 'solid'],
  44. dashed: ['default', 'dashed'],
  45. // `link` is not a real color but we should compatible with it
  46. link: ['link', 'link'],
  47. text: ['default', 'text']
  48. };
  49. const InternalCompoundedButton = /*#__PURE__*/React.forwardRef((props, ref) => {
  50. var _a, _b;
  51. const {
  52. loading = false,
  53. prefixCls: customizePrefixCls,
  54. color,
  55. variant,
  56. type,
  57. danger = false,
  58. shape: customizeShape,
  59. size: customizeSize,
  60. styles,
  61. disabled: customDisabled,
  62. className,
  63. rootClassName,
  64. children,
  65. icon,
  66. iconPosition = 'start',
  67. ghost = false,
  68. block = false,
  69. // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
  70. htmlType = 'button',
  71. classNames: customClassNames,
  72. style: customStyle = {},
  73. autoInsertSpace,
  74. autoFocus
  75. } = props,
  76. rest = __rest(props, ["loading", "prefixCls", "color", "variant", "type", "danger", "shape", "size", "styles", "disabled", "className", "rootClassName", "children", "icon", "iconPosition", "ghost", "block", "htmlType", "classNames", "style", "autoInsertSpace", "autoFocus"]);
  77. // https://github.com/ant-design/ant-design/issues/47605
  78. // Compatible with original `type` behavior
  79. const mergedType = type || 'default';
  80. const {
  81. button
  82. } = React.useContext(ConfigContext);
  83. const shape = customizeShape || (button === null || button === void 0 ? void 0 : button.shape) || 'default';
  84. const [mergedColor, mergedVariant] = useMemo(() => {
  85. // >>>>> Local
  86. // Color & Variant
  87. if (color && variant) {
  88. return [color, variant];
  89. }
  90. // Sugar syntax
  91. if (type || danger) {
  92. const colorVariantPair = ButtonTypeMap[mergedType] || [];
  93. if (danger) {
  94. return ['danger', colorVariantPair[1]];
  95. }
  96. return colorVariantPair;
  97. }
  98. // >>> Context fallback
  99. if ((button === null || button === void 0 ? void 0 : button.color) && (button === null || button === void 0 ? void 0 : button.variant)) {
  100. return [button.color, button.variant];
  101. }
  102. return ['default', 'outlined'];
  103. }, [type, color, variant, danger, button === null || button === void 0 ? void 0 : button.variant, button === null || button === void 0 ? void 0 : button.color]);
  104. const isDanger = mergedColor === 'danger';
  105. const mergedColorText = isDanger ? 'dangerous' : mergedColor;
  106. const {
  107. getPrefixCls,
  108. direction,
  109. autoInsertSpace: contextAutoInsertSpace,
  110. className: contextClassName,
  111. style: contextStyle,
  112. classNames: contextClassNames,
  113. styles: contextStyles
  114. } = useComponentConfig('button');
  115. const mergedInsertSpace = (_a = autoInsertSpace !== null && autoInsertSpace !== void 0 ? autoInsertSpace : contextAutoInsertSpace) !== null && _a !== void 0 ? _a : true;
  116. const prefixCls = getPrefixCls('btn', customizePrefixCls);
  117. const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
  118. const disabled = useContext(DisabledContext);
  119. const mergedDisabled = customDisabled !== null && customDisabled !== void 0 ? customDisabled : disabled;
  120. const groupSize = useContext(GroupSizeContext);
  121. const loadingOrDelay = useMemo(() => getLoadingConfig(loading), [loading]);
  122. const [innerLoading, setLoading] = useState(loadingOrDelay.loading);
  123. const [hasTwoCNChar, setHasTwoCNChar] = useState(false);
  124. const buttonRef = useRef(null);
  125. const mergedRef = useComposeRef(ref, buttonRef);
  126. const needInserted = Children.count(children) === 1 && !icon && !isUnBorderedButtonVariant(mergedVariant);
  127. // ========================= Mount ==========================
  128. // Record for mount status.
  129. // This will help to no to show the animation of loading on the first mount.
  130. const isMountRef = useRef(true);
  131. React.useEffect(() => {
  132. isMountRef.current = false;
  133. return () => {
  134. isMountRef.current = true;
  135. };
  136. }, []);
  137. // ========================= Effect =========================
  138. // Loading. Should use `useLayoutEffect` to avoid low perf multiple click issue.
  139. // https://github.com/ant-design/ant-design/issues/51325
  140. useLayoutEffect(() => {
  141. let delayTimer = null;
  142. if (loadingOrDelay.delay > 0) {
  143. delayTimer = setTimeout(() => {
  144. delayTimer = null;
  145. setLoading(true);
  146. }, loadingOrDelay.delay);
  147. } else {
  148. setLoading(loadingOrDelay.loading);
  149. }
  150. function cleanupTimer() {
  151. if (delayTimer) {
  152. clearTimeout(delayTimer);
  153. delayTimer = null;
  154. }
  155. }
  156. return cleanupTimer;
  157. }, [loadingOrDelay.delay, loadingOrDelay.loading]);
  158. // Two chinese characters check
  159. useEffect(() => {
  160. // FIXME: for HOC usage like <FormatMessage />
  161. if (!buttonRef.current || !mergedInsertSpace) {
  162. return;
  163. }
  164. const buttonText = buttonRef.current.textContent || '';
  165. if (needInserted && isTwoCNChar(buttonText)) {
  166. if (!hasTwoCNChar) {
  167. setHasTwoCNChar(true);
  168. }
  169. } else if (hasTwoCNChar) {
  170. setHasTwoCNChar(false);
  171. }
  172. });
  173. // Auto focus
  174. useEffect(() => {
  175. if (autoFocus && buttonRef.current) {
  176. buttonRef.current.focus();
  177. }
  178. }, []);
  179. // ========================= Events =========================
  180. const handleClick = React.useCallback(e => {
  181. var _a;
  182. // FIXME: https://github.com/ant-design/ant-design/issues/30207
  183. if (innerLoading || mergedDisabled) {
  184. e.preventDefault();
  185. return;
  186. }
  187. (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props, 'href' in props ? e : e);
  188. }, [props.onClick, innerLoading, mergedDisabled]);
  189. // ========================== Warn ==========================
  190. if (process.env.NODE_ENV !== 'production') {
  191. const warning = devUseWarning('Button');
  192. process.env.NODE_ENV !== "production" ? warning(!(typeof icon === 'string' && icon.length > 2), 'breaking', `\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`) : void 0;
  193. process.env.NODE_ENV !== "production" ? warning(!(ghost && isUnBorderedButtonVariant(mergedVariant)), 'usage', "`link` or `text` button can't be a `ghost` button.") : void 0;
  194. }
  195. // ========================== Size ==========================
  196. const {
  197. compactSize,
  198. compactItemClassnames
  199. } = useCompactItemContext(prefixCls, direction);
  200. const sizeClassNameMap = {
  201. large: 'lg',
  202. small: 'sm',
  203. middle: undefined
  204. };
  205. const sizeFullName = useSize(ctxSize => {
  206. var _a, _b;
  207. return (_b = (_a = customizeSize !== null && customizeSize !== void 0 ? customizeSize : compactSize) !== null && _a !== void 0 ? _a : groupSize) !== null && _b !== void 0 ? _b : ctxSize;
  208. });
  209. const sizeCls = sizeFullName ? (_b = sizeClassNameMap[sizeFullName]) !== null && _b !== void 0 ? _b : '' : '';
  210. const iconType = innerLoading ? 'loading' : icon;
  211. const linkButtonRestProps = omit(rest, ['navigate']);
  212. // ========================= Render =========================
  213. const classes = classNames(prefixCls, hashId, cssVarCls, {
  214. [`${prefixCls}-${shape}`]: shape !== 'default' && shape,
  215. // Compatible with versions earlier than 5.21.0
  216. [`${prefixCls}-${mergedType}`]: mergedType,
  217. [`${prefixCls}-dangerous`]: danger,
  218. [`${prefixCls}-color-${mergedColorText}`]: mergedColorText,
  219. [`${prefixCls}-variant-${mergedVariant}`]: mergedVariant,
  220. [`${prefixCls}-${sizeCls}`]: sizeCls,
  221. [`${prefixCls}-icon-only`]: !children && children !== 0 && !!iconType,
  222. [`${prefixCls}-background-ghost`]: ghost && !isUnBorderedButtonVariant(mergedVariant),
  223. [`${prefixCls}-loading`]: innerLoading,
  224. [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && mergedInsertSpace && !innerLoading,
  225. [`${prefixCls}-block`]: block,
  226. [`${prefixCls}-rtl`]: direction === 'rtl',
  227. [`${prefixCls}-icon-end`]: iconPosition === 'end'
  228. }, compactItemClassnames, className, rootClassName, contextClassName);
  229. const fullStyle = Object.assign(Object.assign({}, contextStyle), customStyle);
  230. const iconClasses = classNames(customClassNames === null || customClassNames === void 0 ? void 0 : customClassNames.icon, contextClassNames.icon);
  231. const iconStyle = Object.assign(Object.assign({}, (styles === null || styles === void 0 ? void 0 : styles.icon) || {}), contextStyles.icon || {});
  232. const iconNode = icon && !innerLoading ? (/*#__PURE__*/React.createElement(IconWrapper, {
  233. prefixCls: prefixCls,
  234. className: iconClasses,
  235. style: iconStyle
  236. }, icon)) : loading && typeof loading === 'object' && loading.icon ? (/*#__PURE__*/React.createElement(IconWrapper, {
  237. prefixCls: prefixCls,
  238. className: iconClasses,
  239. style: iconStyle
  240. }, loading.icon)) : (/*#__PURE__*/React.createElement(DefaultLoadingIcon, {
  241. existIcon: !!icon,
  242. prefixCls: prefixCls,
  243. loading: innerLoading,
  244. mount: isMountRef.current
  245. }));
  246. const kids = children || children === 0 ? spaceChildren(children, needInserted && mergedInsertSpace) : null;
  247. if (linkButtonRestProps.href !== undefined) {
  248. return wrapCSSVar(/*#__PURE__*/React.createElement("a", Object.assign({}, linkButtonRestProps, {
  249. className: classNames(classes, {
  250. [`${prefixCls}-disabled`]: mergedDisabled
  251. }),
  252. href: mergedDisabled ? undefined : linkButtonRestProps.href,
  253. style: fullStyle,
  254. onClick: handleClick,
  255. ref: mergedRef,
  256. tabIndex: mergedDisabled ? -1 : 0,
  257. "aria-disabled": mergedDisabled
  258. }), iconNode, kids));
  259. }
  260. let buttonNode = /*#__PURE__*/React.createElement("button", Object.assign({}, rest, {
  261. type: htmlType,
  262. className: classes,
  263. style: fullStyle,
  264. onClick: handleClick,
  265. disabled: mergedDisabled,
  266. ref: mergedRef
  267. }), iconNode, kids, compactItemClassnames && /*#__PURE__*/React.createElement(Compact, {
  268. prefixCls: prefixCls
  269. }));
  270. if (!isUnBorderedButtonVariant(mergedVariant)) {
  271. buttonNode = /*#__PURE__*/React.createElement(Wave, {
  272. component: "Button",
  273. disabled: innerLoading
  274. }, buttonNode);
  275. }
  276. return wrapCSSVar(buttonNode);
  277. });
  278. const Button = InternalCompoundedButton;
  279. Button.Group = Group;
  280. Button.__ANT_BUTTON = true;
  281. if (process.env.NODE_ENV !== 'production') {
  282. Button.displayName = 'Button';
  283. }
  284. export default Button;