WaveEffect.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. "use client";
  2. import * as React from 'react';
  3. import classNames from 'classnames';
  4. import CSSMotion from 'rc-motion';
  5. import raf from "rc-util/es/raf";
  6. import { composeRef } from "rc-util/es/ref";
  7. import { unstableSetRender } from '../../config-provider/UnstableContext';
  8. import { TARGET_CLS } from './interface';
  9. import { getTargetWaveColor } from './util';
  10. function validateNum(value) {
  11. return Number.isNaN(value) ? 0 : value;
  12. }
  13. const WaveEffect = props => {
  14. const {
  15. className,
  16. target,
  17. component,
  18. registerUnmount
  19. } = props;
  20. const divRef = React.useRef(null);
  21. // ====================== Refs ======================
  22. const unmountRef = React.useRef(null);
  23. React.useEffect(() => {
  24. unmountRef.current = registerUnmount();
  25. }, []);
  26. // ===================== Effect =====================
  27. const [color, setWaveColor] = React.useState(null);
  28. const [borderRadius, setBorderRadius] = React.useState([]);
  29. const [left, setLeft] = React.useState(0);
  30. const [top, setTop] = React.useState(0);
  31. const [width, setWidth] = React.useState(0);
  32. const [height, setHeight] = React.useState(0);
  33. const [enabled, setEnabled] = React.useState(false);
  34. const waveStyle = {
  35. left,
  36. top,
  37. width,
  38. height,
  39. borderRadius: borderRadius.map(radius => `${radius}px`).join(' ')
  40. };
  41. if (color) {
  42. waveStyle['--wave-color'] = color;
  43. }
  44. function syncPos() {
  45. const nodeStyle = getComputedStyle(target);
  46. // Get wave color from target
  47. setWaveColor(getTargetWaveColor(target));
  48. const isStatic = nodeStyle.position === 'static';
  49. // Rect
  50. const {
  51. borderLeftWidth,
  52. borderTopWidth
  53. } = nodeStyle;
  54. setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth)));
  55. setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth)));
  56. setWidth(target.offsetWidth);
  57. setHeight(target.offsetHeight);
  58. // Get border radius
  59. const {
  60. borderTopLeftRadius,
  61. borderTopRightRadius,
  62. borderBottomLeftRadius,
  63. borderBottomRightRadius
  64. } = nodeStyle;
  65. setBorderRadius([borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius].map(radius => validateNum(parseFloat(radius))));
  66. }
  67. React.useEffect(() => {
  68. if (target) {
  69. // We need delay to check position here
  70. // since UI may change after click
  71. const id = raf(() => {
  72. syncPos();
  73. setEnabled(true);
  74. });
  75. // Add resize observer to follow size
  76. let resizeObserver;
  77. if (typeof ResizeObserver !== 'undefined') {
  78. resizeObserver = new ResizeObserver(syncPos);
  79. resizeObserver.observe(target);
  80. }
  81. return () => {
  82. raf.cancel(id);
  83. resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
  84. };
  85. }
  86. }, []);
  87. if (!enabled) {
  88. return null;
  89. }
  90. const isSmallComponent = (component === 'Checkbox' || component === 'Radio') && (target === null || target === void 0 ? void 0 : target.classList.contains(TARGET_CLS));
  91. return /*#__PURE__*/React.createElement(CSSMotion, {
  92. visible: true,
  93. motionAppear: true,
  94. motionName: "wave-motion",
  95. motionDeadline: 5000,
  96. onAppearEnd: (_, event) => {
  97. var _a, _b;
  98. if (event.deadline || event.propertyName === 'opacity') {
  99. const holder = (_a = divRef.current) === null || _a === void 0 ? void 0 : _a.parentElement;
  100. (_b = unmountRef.current) === null || _b === void 0 ? void 0 : _b.call(unmountRef).then(() => {
  101. holder === null || holder === void 0 ? void 0 : holder.remove();
  102. });
  103. }
  104. return false;
  105. }
  106. }, ({
  107. className: motionClassName
  108. }, ref) => (/*#__PURE__*/React.createElement("div", {
  109. ref: composeRef(divRef, ref),
  110. className: classNames(className, motionClassName, {
  111. 'wave-quick': isSmallComponent
  112. }),
  113. style: waveStyle
  114. })));
  115. };
  116. const showWaveEffect = (target, info) => {
  117. var _a;
  118. const {
  119. component
  120. } = info;
  121. // Skip for unchecked checkbox
  122. if (component === 'Checkbox' && !((_a = target.querySelector('input')) === null || _a === void 0 ? void 0 : _a.checked)) {
  123. return;
  124. }
  125. // Create holder
  126. const holder = document.createElement('div');
  127. holder.style.position = 'absolute';
  128. holder.style.left = '0px';
  129. holder.style.top = '0px';
  130. target === null || target === void 0 ? void 0 : target.insertBefore(holder, target === null || target === void 0 ? void 0 : target.firstChild);
  131. const reactRender = unstableSetRender();
  132. let unmountCallback = null;
  133. function registerUnmount() {
  134. return unmountCallback;
  135. }
  136. unmountCallback = reactRender(/*#__PURE__*/React.createElement(WaveEffect, Object.assign({}, info, {
  137. target: target,
  138. registerUnmount: registerUnmount
  139. })), holder);
  140. };
  141. export default showWaveEffect;