@tendaui/components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/alert/Alert.tsx +147 -0
  4. package/alert/defaultProps.ts +3 -0
  5. package/alert/index.ts +9 -0
  6. package/alert/style/css.js +1 -0
  7. package/alert/style/index.js +1 -0
  8. package/alert/type.ts +44 -0
  9. package/badge/Badge.tsx +85 -0
  10. package/badge/defaultProps.ts +10 -0
  11. package/badge/index.ts +9 -0
  12. package/badge/style/css.js +1 -0
  13. package/badge/style/index.js +1 -0
  14. package/badge/type.ts +51 -0
  15. package/button/Button.tsx +95 -0
  16. package/button/defaultProps.ts +13 -0
  17. package/button/index.ts +7 -0
  18. package/button/style/css.js +1 -0
  19. package/button/style/index.js +1 -0
  20. package/button/type.ts +82 -0
  21. package/checkbox/Checkbox.tsx +19 -0
  22. package/checkbox/CheckboxGroup.tsx +207 -0
  23. package/checkbox/defaultProps.ts +14 -0
  24. package/checkbox/index.ts +10 -0
  25. package/checkbox/style/css.js +1 -0
  26. package/checkbox/style/index.js +1 -0
  27. package/checkbox/type.ts +117 -0
  28. package/common/Check.tsx +131 -0
  29. package/common/FakeArrow.tsx +36 -0
  30. package/common/PluginContainer.tsx +21 -0
  31. package/common/Portal.tsx +67 -0
  32. package/common.ts +76 -0
  33. package/config-provider/ConfigContext.tsx +21 -0
  34. package/config-provider/ConfigProvider.tsx +53 -0
  35. package/config-provider/index.ts +9 -0
  36. package/config-provider/type.ts +1062 -0
  37. package/dialog/Dialog.tsx +254 -0
  38. package/dialog/DialogCard.tsx +152 -0
  39. package/dialog/defaultProps.ts +25 -0
  40. package/dialog/hooks/useDialogDrag.ts +50 -0
  41. package/dialog/hooks/useDialogEsc.ts +31 -0
  42. package/dialog/hooks/useDialogPosition.ts +36 -0
  43. package/dialog/hooks/useLockStyle.ts +54 -0
  44. package/dialog/index.ts +13 -0
  45. package/dialog/plugin.tsx +78 -0
  46. package/dialog/style/css.js +1 -0
  47. package/dialog/style/index.js +1 -0
  48. package/dialog/type.ts +241 -0
  49. package/dialog/utils.ts +4 -0
  50. package/form/Form.tsx +136 -0
  51. package/form/FormContext.tsx +64 -0
  52. package/form/FormItem.tsx +554 -0
  53. package/form/FormList.tsx +303 -0
  54. package/form/const.ts +6 -0
  55. package/form/defaultProps.ts +26 -0
  56. package/form/formModel.ts +117 -0
  57. package/form/hooks/interface.ts +20 -0
  58. package/form/hooks/useForm.ts +122 -0
  59. package/form/hooks/useFormItemInitialData.ts +95 -0
  60. package/form/hooks/useFormItemStyle.tsx +122 -0
  61. package/form/hooks/useInstance.tsx +275 -0
  62. package/form/hooks/useWatch.ts +42 -0
  63. package/form/index.ts +11 -0
  64. package/form/style/css.js +1 -0
  65. package/form/style/index.js +1 -0
  66. package/form/type.ts +519 -0
  67. package/form/utils/index.ts +69 -0
  68. package/hooks/useAttach.ts +24 -0
  69. package/hooks/useCommonClassName.ts +45 -0
  70. package/hooks/useConfig.ts +3 -0
  71. package/hooks/useControlled.ts +39 -0
  72. package/hooks/useDefaultProps.ts +16 -0
  73. package/hooks/useDomCallback.ts +13 -0
  74. package/hooks/useDomRefCallback.ts +12 -0
  75. package/hooks/useDragSorter.tsx +151 -0
  76. package/hooks/useEventCallback.ts +47 -0
  77. package/hooks/useGlobalConfig.ts +14 -0
  78. package/hooks/useGlobalIcon.ts +14 -0
  79. package/hooks/useLastest.ts +13 -0
  80. package/hooks/useLayoutEffect.ts +7 -0
  81. package/hooks/useMouseEvent.ts +142 -0
  82. package/hooks/useMutationObserver.ts +56 -0
  83. package/hooks/usePopper.ts +189 -0
  84. package/hooks/useRipple.ts +0 -0
  85. package/hooks/useSetState.ts +25 -0
  86. package/hooks/useVirtualScroll.ts +246 -0
  87. package/hooks/useWindowSize.ts +31 -0
  88. package/index.ts +70 -0
  89. package/input/Input.tsx +383 -0
  90. package/input/InputGroup.tsx +29 -0
  91. package/input/defaultProps.ts +22 -0
  92. package/input/index.ts +11 -0
  93. package/input/style/css.js +1 -0
  94. package/input/style/index.js +1 -0
  95. package/input/type.ts +219 -0
  96. package/loading/Gradient.tsx +36 -0
  97. package/loading/Loading.tsx +169 -0
  98. package/loading/circleAdapter.ts +44 -0
  99. package/loading/defaultProps.ts +12 -0
  100. package/loading/index.ts +13 -0
  101. package/loading/style/css.js +1 -0
  102. package/loading/style/index.js +1 -0
  103. package/loading/type.ts +71 -0
  104. package/loading/utils/setStyle.ts +13 -0
  105. package/myform/index.ts +0 -0
  106. package/notification/Notify.ts +24 -0
  107. package/notification/NotifyContainer.tsx +90 -0
  108. package/notification/NotifyContext.tsx +173 -0
  109. package/notification/NotifyItem.tsx +121 -0
  110. package/notification/index.ts +3 -0
  111. package/notification/style/css.js +1 -0
  112. package/notification/style/index.js +1 -0
  113. package/notification/type.ts +23 -0
  114. package/package.json +52 -0
  115. package/popup/Popup.tsx +264 -0
  116. package/popup/defaultProps.ts +13 -0
  117. package/popup/hooks/useTrigger.ts +276 -0
  118. package/popup/index.ts +6 -0
  119. package/popup/style/css.js +1 -0
  120. package/popup/style/index.js +1 -0
  121. package/popup/type.ts +130 -0
  122. package/portal/Portal.tsx +63 -0
  123. package/portal/index.ts +1 -0
  124. package/select/Option.tsx +162 -0
  125. package/select/OptionGroup.tsx +30 -0
  126. package/select/PopupContent.tsx +271 -0
  127. package/select/Select.tsx +586 -0
  128. package/select/defaultProps.ts +27 -0
  129. package/select/hooks/useOptions.ts +120 -0
  130. package/select/hooks/usePanelVirtualScroll.ts +111 -0
  131. package/select/index.ts +9 -0
  132. package/select/style/css.js +1 -0
  133. package/select/style/index.js +2 -0
  134. package/select/type.ts +382 -0
  135. package/select/utils/helper.ts +256 -0
  136. package/select-input/SelectInput.tsx +98 -0
  137. package/select-input/defaultProps.ts +15 -0
  138. package/select-input/hook/useMultiple.tsx +100 -0
  139. package/select-input/hook/useOverlayInnerStyle.ts +84 -0
  140. package/select-input/hook/useSingle.tsx +112 -0
  141. package/select-input/index.ts +6 -0
  142. package/select-input/interface.ts +18 -0
  143. package/select-input/style/css.js +1 -0
  144. package/select-input/style/index.js +1 -0
  145. package/select-input/type.ts +280 -0
  146. package/space/defaultProps.ts +0 -0
  147. package/space/index.ts +0 -0
  148. package/space/type.ts +0 -0
  149. package/style/index.js +2 -0
  150. package/styles/_global.scss +39 -0
  151. package/styles/_vars.scss +386 -0
  152. package/styles/components/alert/_index.scss +175 -0
  153. package/styles/components/alert/_vars.scss +39 -0
  154. package/styles/components/badge/_index.scss +70 -0
  155. package/styles/components/badge/_vars.scss +25 -0
  156. package/styles/components/button/_index.scss +511 -0
  157. package/styles/components/button/_mixins.scss +39 -0
  158. package/styles/components/button/_vars.scss +122 -0
  159. package/styles/components/checkbox/_index.scss +158 -0
  160. package/styles/components/checkbox/_mixin.scss +0 -0
  161. package/styles/components/checkbox/_var.scss +60 -0
  162. package/styles/components/dialog/_animate.scss +135 -0
  163. package/styles/components/dialog/_index.scss +311 -0
  164. package/styles/components/dialog/_mixins.scss +0 -0
  165. package/styles/components/dialog/_vars.scss +59 -0
  166. package/styles/components/form/_index.scss +174 -0
  167. package/styles/components/form/_mixins.scss +76 -0
  168. package/styles/components/form/_vars.scss +100 -0
  169. package/styles/components/input/_index.scss +349 -0
  170. package/styles/components/input/_map.scss +0 -0
  171. package/styles/components/input/_mixins.scss +116 -0
  172. package/styles/components/input/_vars.scss +134 -0
  173. package/styles/components/loading/_index.scss +112 -0
  174. package/styles/components/loading/_vars.scss +39 -0
  175. package/styles/components/notification/_index.scss +160 -0
  176. package/styles/components/notification/_mixins.scss +12 -0
  177. package/styles/components/notification/_vars.scss +59 -0
  178. package/styles/components/popup/_index.scss +82 -0
  179. package/styles/components/popup/_mixin.scss +149 -0
  180. package/styles/components/popup/_var.scss +31 -0
  181. package/styles/components/select/_index.scss +290 -0
  182. package/styles/components/select/_var.scss +65 -0
  183. package/styles/components/select-input/_index.scss +5 -0
  184. package/styles/components/select-input/_var.scss +3 -0
  185. package/styles/components/switch/_index.scss +279 -0
  186. package/styles/components/switch/_mixins.scss +0 -0
  187. package/styles/components/switch/_vars.scss +61 -0
  188. package/styles/components/tag/_index.scss +316 -0
  189. package/styles/components/tag/_var.scss +85 -0
  190. package/styles/components/tag-input/_index.scss +163 -0
  191. package/styles/components/tag-input/_vars.scss +16 -0
  192. package/styles/globals.css +250 -0
  193. package/styles/mixins/_focus.scss +7 -0
  194. package/styles/mixins/_layout.scss +32 -0
  195. package/styles/mixins/_reset.scss +10 -0
  196. package/styles/mixins/_scrollbar.scss +31 -0
  197. package/styles/mixins/_text.scss +48 -0
  198. package/styles/rillple.css +16 -0
  199. package/styles/scrollbar.css +42 -0
  200. package/styles/themes/_dark.scss +191 -0
  201. package/styles/themes/_font.scss +79 -0
  202. package/styles/themes/_index.scss +5 -0
  203. package/styles/themes/_light.scss +190 -0
  204. package/styles/themes/_radius.scss +9 -0
  205. package/styles/themes/_size.scss +68 -0
  206. package/styles/themes.css +66 -0
  207. package/styles/utilities/_animation.scss +57 -0
  208. package/styles/utilities/_tips.scss +9 -0
  209. package/switch/Switch.tsx +120 -0
  210. package/switch/defaultProps.ts +3 -0
  211. package/switch/index.ts +7 -0
  212. package/switch/style/css.js +1 -0
  213. package/switch/style/index.js +1 -0
  214. package/switch/type.ts +46 -0
  215. package/tag/Tag.tsx +149 -0
  216. package/tag/defaultProps.ts +19 -0
  217. package/tag/index.ts +8 -0
  218. package/tag/style/css.js +1 -0
  219. package/tag/style/index.js +1 -0
  220. package/tag/type.ts +170 -0
  221. package/tag-input/TagInput.tsx +215 -0
  222. package/tag-input/defaultProps.ts +15 -0
  223. package/tag-input/hooks/useHover.ts +28 -0
  224. package/tag-input/hooks/useTagList.tsx +131 -0
  225. package/tag-input/hooks/useTagScroll.ts +105 -0
  226. package/tag-input/index.ts +9 -0
  227. package/tag-input/style/css.js +1 -0
  228. package/tag-input/style/index.js +1 -0
  229. package/tag-input/type.ts +224 -0
  230. package/tag-input/useTagList.tsx +131 -0
  231. package/utils/composeRefs.ts +14 -0
  232. package/utils/dom.ts +29 -0
  233. package/utils/forwardRefWithStatics.ts +12 -0
  234. package/utils/getScrollbarWidth.ts +11 -0
  235. package/utils/helper.ts +161 -0
  236. package/utils/isFragment.ts +22 -0
  237. package/utils/listener.ts +37 -0
  238. package/utils/noop.ts +3 -0
  239. package/utils/parentTNode.ts +38 -0
  240. package/utils/parseTNode.ts +38 -0
  241. package/utils/react-render.ts +108 -0
  242. package/utils/ref.ts +6 -0
  243. package/utils/refs.ts +81 -0
  244. package/utils/style.ts +60 -0
  245. package/utils/transition.ts +28 -0
@@ -0,0 +1,121 @@
1
+ import React, { useLayoutEffect, useRef, useState } from "react";
2
+ import { TdNotificationProps, HeightItem } from "./type";
3
+ import { IconClose, IconInfoCircle, IconCheckCircleStroked, IconAlertTriangle } from "@tendaui/icons";
4
+ import useConfig from "../hooks/useConfig";
5
+
6
+ const NotificationItem: React.FC<TdNotificationProps> = ({
7
+ message,
8
+ type,
9
+ heights,
10
+ setHeights,
11
+ id,
12
+ gap,
13
+ isExpanded,
14
+ isRemoved,
15
+ title
16
+ }) => {
17
+ const [initialHeight, setInitialHeight] = useState(0);
18
+ const [isMounted, setIsMounted] = useState(false);
19
+ const nofityItem = useRef<HTMLDivElement>(null);
20
+ const { classPrefix: prefix } = useConfig();
21
+ React.useEffect(() => {
22
+ setIsMounted(true);
23
+ }, []);
24
+
25
+ React.useEffect(() => {
26
+ const notifyNode = nofityItem.current;
27
+ if (notifyNode) {
28
+ const height = notifyNode.getBoundingClientRect().height;
29
+ setInitialHeight(height);
30
+ setHeights((h: HeightItem[]) => {
31
+ // 如果不存在,则添加
32
+ return [{ toastId: id, height, message, type }, ...h];
33
+ });
34
+ return () => {
35
+ setHeights((h: HeightItem[]) => h.filter((h) => h.toastId !== id));
36
+ };
37
+ }
38
+ }, [setHeights, id, message, type]);
39
+
40
+ useLayoutEffect(() => {
41
+ if (!isMounted) return;
42
+ const notifyNode = nofityItem.current;
43
+ if (notifyNode) {
44
+ const originalHeight = notifyNode.style.height;
45
+ notifyNode.style.height = "auto";
46
+ const newHeight = notifyNode.getBoundingClientRect().height;
47
+ notifyNode.style.height = originalHeight;
48
+ setInitialHeight(newHeight);
49
+ setHeights((heights) => {
50
+ const isExist = heights.some((h) => h.toastId === id);
51
+ if (isExist) {
52
+ // 如果存在,则更新高度
53
+ return heights.map((h) => (h.toastId === id ? { ...h, height: newHeight, message, type } : h));
54
+ }
55
+ // 如果不存在,则添加
56
+ return [{ toastId: id, height: newHeight, message, type }, ...heights];
57
+ });
58
+ }
59
+ }, [isMounted, initialHeight, setHeights, id, message, type]);
60
+ const heightIndex = React.useMemo(() => {
61
+ return heights.findIndex((h) => h.toastId === id);
62
+ }, [heights, id]);
63
+ const toastHeightBefore = React.useMemo(() => {
64
+ return heights.reduce((acc, h, reduceIndex) => {
65
+ if (reduceIndex < heightIndex) {
66
+ return acc + h.height;
67
+ }
68
+ return acc;
69
+ }, 0);
70
+ }, [heightIndex, heights]);
71
+
72
+ const offset = React.useMemo(() => heightIndex * gap + toastHeightBefore, [toastHeightBefore, heightIndex, gap]);
73
+
74
+ return (
75
+ <div
76
+ className={`${prefix}-notify__item`}
77
+ ref={nofityItem}
78
+ style={
79
+ {
80
+ position: "absolute",
81
+ height: isExpanded ? `auto` : `var(--front-toast-height)`,
82
+ width: `var(--toast-width)`,
83
+ "--offset": offset + "px",
84
+ "--index": heightIndex,
85
+ "--gap": gap + "px",
86
+ "--z-index": heights.length - heightIndex
87
+ } as React.CSSProperties
88
+ }
89
+ data-toast
90
+ data-mounted={isMounted}
91
+ data-expanded={isExpanded}
92
+ data-removed={isRemoved}
93
+ data-front={heightIndex === 0}
94
+ >
95
+ <div className={`${prefix}-notify__content`}>
96
+ <div className={`${prefix}-notify__header`}>
97
+ <div className={`${prefix}-notify__icon`}>
98
+ {type === "success" && <IconCheckCircleStroked className="t-icon t-is-success" />}
99
+ {type === "error" && <IconClose className="t-icon t-is-error" />}
100
+ {type === "info" && <IconInfoCircle className="t-icon t-is-info" />}
101
+ {type === "warning" && <IconAlertTriangle className="t-icon t-is-warning" />}
102
+ {type === "default" && <IconInfoCircle className="t-icon t-is-info" />}
103
+ </div>
104
+
105
+ <div className={`${prefix}-notify__title`}>{title}</div>
106
+ </div>
107
+ </div>
108
+ <p
109
+ className={`${prefix}-notify__detail`}
110
+ style={{
111
+ opacity: heightIndex === 0 || isExpanded ? 1 : 0,
112
+ transition: `opacity 400ms`
113
+ }}
114
+ >
115
+ {message}
116
+ </p>
117
+ </div>
118
+ );
119
+ };
120
+
121
+ export default NotificationItem;
@@ -0,0 +1,3 @@
1
+ export { notification } from "./Notify";
2
+ export { NotificationProvider, useNotification } from "./NotifyContext";
3
+ import "./style/index";
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/notification/_index.scss";
@@ -0,0 +1,23 @@
1
+ export interface TdNotificationProps {
2
+ id: string;
3
+ type: string;
4
+ message: string;
5
+ title: string;
6
+ onRemove?: (id: string) => void;
7
+ onHoverStart?: () => void;
8
+ onHoverEnd?: () => void;
9
+ maxStack?: number;
10
+ position?: string;
11
+ heights?: HeightItem[];
12
+ setHeights?: React.Dispatch<React.SetStateAction<HeightItem[]>>;
13
+ gap?: number;
14
+ isExpanded?: boolean;
15
+ isRemoved?: boolean;
16
+ }
17
+
18
+ export interface HeightItem {
19
+ toastId: string;
20
+ height: number;
21
+ message: string;
22
+ type: string;
23
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@tendaui/components",
3
+ "version": "1.0.0",
4
+ "description": "TendaUI React Components - Source code",
5
+ "main": "index.ts",
6
+ "module": "index.ts",
7
+ "types": "index.ts",
8
+ "scripts": {
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "peerDependencies": {
12
+ "react": ">=16.13.1",
13
+ "react-dom": ">=16.13.1"
14
+ },
15
+ "keywords": [
16
+ "react",
17
+ "ui",
18
+ "component",
19
+ "tendaui",
20
+ "components"
21
+ ],
22
+ "author": "ajiangz",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/ajiangpz/react-ui.git",
27
+ "directory": "packages/components"
28
+ },
29
+ "homepage": "https://github.com/ajiangpz/react-ui#readme",
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "files": [
35
+ "**/*.ts",
36
+ "**/*.tsx",
37
+ "**/*.js",
38
+ "**/*.jsx",
39
+ "**/*.scss",
40
+ "**/*.css",
41
+ "!**/*.stories.tsx",
42
+ "!**/*.test.ts",
43
+ "!**/*.test.tsx",
44
+ "!**/*.spec.ts",
45
+ "!**/*.spec.tsx"
46
+ ],
47
+ "sideEffects": [
48
+ "**/*.scss",
49
+ "**/*.css"
50
+ ],
51
+ "gitHead": "31b15457f07146ef1604c713b32addd944fbb6dd"
52
+ }
@@ -0,0 +1,264 @@
1
+ import { Placement, type Options } from "@popperjs/core";
2
+ import { debounce, isFunction } from "lodash-es";
3
+ import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
4
+ import { getRefDom } from "../utils/ref";
5
+ import Portal from "../portal/Portal";
6
+ import useControlled from "../hooks/useControlled";
7
+ import useDefaultProps from "../hooks/useDefaultProps";
8
+ import useMutationObserver from "../hooks/useMutationObserver";
9
+ import useWindowSize from "../hooks/useWindowSize";
10
+ import useTrigger from "./hooks/useTrigger";
11
+ import type { TdPopupProps } from "./type";
12
+ import usePopper from "../hooks/usePopper";
13
+ import { popupDefaultProps } from "./defaultProps";
14
+ import classNames from "classnames";
15
+ import { CSSTransition } from "react-transition-group";
16
+ import { getCssVarsValue } from "../utils/style";
17
+ import useConfig from "../hooks/useConfig";
18
+
19
+ export interface PopupProps extends TdPopupProps {
20
+ // 是否触发展开收起动画,内部下拉式组件使用
21
+ expandAnimation?: boolean;
22
+ // 更新内容区域滚动条
23
+ updateScrollTop?: (content: HTMLDivElement) => void;
24
+ }
25
+
26
+ export interface PopupRef {
27
+ /** 获取 popper 实例 */
28
+ getPopper: () => ReturnType<typeof usePopper>;
29
+ /** 获取 Popup dom 元素 */
30
+ getPopupElement: () => HTMLDivElement;
31
+ /** 获取 portal dom 元素 */
32
+ getPortalElement: () => HTMLDivElement;
33
+ /** 获取内容区域 dom 元素 */
34
+ getPopupContentElement: () => HTMLDivElement;
35
+ /** 设置 popup 显示隐藏 */
36
+ setVisible: (visible: boolean) => void;
37
+ }
38
+
39
+ const Popup = forwardRef<PopupRef, PopupProps>((originalProps, ref) => {
40
+ const props = useDefaultProps<PopupProps>(originalProps, popupDefaultProps);
41
+ const { classPrefix } = useConfig();
42
+ const {
43
+ trigger,
44
+ content,
45
+ placement,
46
+ showArrow,
47
+ destroyOnClose,
48
+ overlayClassName,
49
+ overlayInnerClassName,
50
+ overlayStyle,
51
+ overlayInnerStyle,
52
+ triggerElement,
53
+ children = triggerElement,
54
+ disabled,
55
+ zIndex,
56
+ onScroll,
57
+ onScrollToBottom,
58
+ delay,
59
+ hideEmptyPopup,
60
+ updateScrollTop
61
+ } = props;
62
+
63
+ // 全局配置
64
+ const { height: windowHeight, width: windowWidth } = useWindowSize();
65
+ const [visible, onVisibleChange] = useControlled(props, "visible", props.onVisibleChange);
66
+ // 记录 popup 元素
67
+ // 如果内容为 null 或 undefined,且 hideEmptyPopup 为 true,则不展示 popup
68
+ const [popupElement, setPopupElement] = useState(null);
69
+ const triggerRef = useRef(null); // 记录 trigger 元素
70
+ const popupRef = useRef(null); // popup dom 元素,css transition 需要用
71
+ const portalRef = useRef(null); // portal dom 元素
72
+ const contentRef = useRef(null); // 内容部分
73
+ const popperRef = useRef<ReturnType<typeof usePopper> | null>(null); // 保存 popper 实例
74
+
75
+ // 默认动画时长
76
+ const DEFAULT_TRANSITION_TIMEOUT = 180;
77
+
78
+ // 处理切换 panel 为 null 和正常内容动态切换的情况
79
+ useEffect(() => {
80
+ if (!content && hideEmptyPopup) {
81
+ requestAnimationFrame(() => setPopupElement(null));
82
+ }
83
+ }, [content, hideEmptyPopup]);
84
+
85
+ // 判断展示浮层
86
+ const showOverlay = useMemo(() => {
87
+ if (hideEmptyPopup && !content) return false;
88
+ return visible || popupElement;
89
+ }, [hideEmptyPopup, content, visible, popupElement]);
90
+
91
+ // 转化 placement
92
+ const popperPlacement = useMemo(
93
+ () => placement && (placement.replace(/-(left|top)$/, "-start").replace(/-(right|bottom)$/, "-end") as Placement),
94
+ [placement]
95
+ );
96
+ // 获取 triggerNode
97
+ const { getTriggerNode, getPopupProps, getTriggerDom } = useTrigger({
98
+ triggerRef,
99
+ content,
100
+ disabled,
101
+ trigger,
102
+ visible,
103
+ delay,
104
+ onVisibleChange
105
+ });
106
+ // 传入popperjs 配置选项
107
+ const popperOptions = props.popperOptions as Options;
108
+ const { placement: _ignored, ...restPopperOptions } = popperOptions || {};
109
+ // Suppress unused variable warning
110
+ void _ignored;
111
+
112
+ // popperRef 表示 popper 实例
113
+ popperRef.current = usePopper(getRefDom(triggerRef), popupElement, {
114
+ placement: popperPlacement as Placement,
115
+ ...restPopperOptions
116
+ });
117
+
118
+ // arrow modifier 用于显示箭头
119
+ const hasArrowModifier = popperOptions?.modifiers?.some((modifier) => modifier.name === "arrow");
120
+ const { styles, attributes } = popperRef.current;
121
+ // 获取 triggerNode
122
+ // 如果 children 是函数,则调用函数获取 triggerNode
123
+ // getTriggerNode 设置触发元素的事件
124
+ const triggerNode = isFunction(children) ? getTriggerNode(children({ visible })) : getTriggerNode(children);
125
+
126
+ const updateTimeRef = useRef(null);
127
+ // 监听 trigger 节点或内容变化动态更新 popup 定位
128
+ useMutationObserver(getRefDom(triggerRef), () => {
129
+ const isDisplayNone = getCssVarsValue("display", getRefDom(triggerRef)) === "none";
130
+ if (visible && !isDisplayNone) {
131
+ clearTimeout(updateTimeRef.current);
132
+ updateTimeRef.current = setTimeout(() => popperRef.current?.update?.(), 0);
133
+ }
134
+ });
135
+ // 清理定时器
136
+ useEffect(() => () => clearTimeout(updateTimeRef.current), []);
137
+
138
+ // 窗口尺寸变化时调整位置
139
+ useEffect(() => {
140
+ if (visible) {
141
+ requestAnimationFrame(() => popperRef.current?.update?.());
142
+ }
143
+ }, [visible, content, windowHeight, windowWidth]);
144
+
145
+ // 下拉展开时更新内部滚动条
146
+ useEffect(() => {
147
+ if (!triggerRef.current) triggerRef.current = getTriggerDom();
148
+ if (visible) {
149
+ updateScrollTop?.(contentRef.current);
150
+ }
151
+ }, [visible, updateScrollTop, getTriggerDom]);
152
+
153
+ function handleExited() {
154
+ if (!destroyOnClose && popupElement) {
155
+ (popupElement as HTMLElement).style.display = "none";
156
+ }
157
+ }
158
+ function handleEnter() {
159
+ if (!destroyOnClose && popupElement) {
160
+ popupElement.style.display = "block";
161
+ }
162
+ }
163
+
164
+ function handleScroll(e: React.WheelEvent<HTMLDivElement>) {
165
+ onScroll?.({ e });
166
+
167
+ // 防止多次触发添加截流
168
+ const debounceOnScrollBottom = debounce((e) => onScrollToBottom?.({ e }), 100);
169
+
170
+ const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLDivElement;
171
+ // windows 下滚动会出现小数,所以这里取整
172
+ if (clientHeight + Math.floor(scrollTop) === scrollHeight) {
173
+ // touch bottom
174
+ debounceOnScrollBottom(e);
175
+ }
176
+ }
177
+
178
+ // 整理浮层样式
179
+ function getOverlayStyle(overlayStyle: TdPopupProps["overlayStyle"]) {
180
+ if (getRefDom(triggerRef) && popupRef.current && typeof overlayStyle === "function") {
181
+ return { ...overlayStyle(getRefDom(triggerRef), popupRef.current) };
182
+ }
183
+ return { ...overlayStyle };
184
+ }
185
+
186
+ const overlay = showOverlay && (
187
+ <CSSTransition
188
+ appear
189
+ in={visible}
190
+ timeout={DEFAULT_TRANSITION_TIMEOUT}
191
+ nodeRef={portalRef}
192
+ unmountOnExit={destroyOnClose}
193
+ onEnter={handleEnter}
194
+ onExited={handleExited}
195
+ classNames={`${classPrefix}-portal`}
196
+ >
197
+ <Portal triggerNode={getRefDom(triggerRef)} attach="body" ref={portalRef}>
198
+ <CSSTransition appear timeout={0} in={visible} nodeRef={popupRef} classNames={`${classPrefix}-popup`}>
199
+ <div
200
+ ref={(node) => {
201
+ if (node) {
202
+ popupRef.current = node;
203
+ setPopupElement(node);
204
+ }
205
+ }}
206
+ style={{
207
+ ...styles.popper,
208
+ zIndex,
209
+ ...getOverlayStyle(overlayStyle)
210
+ }}
211
+ className={classNames(`${classPrefix}-popup`, overlayClassName, "")}
212
+ {...attributes.popper}
213
+ {...getPopupProps()}
214
+ >
215
+ <div
216
+ ref={contentRef}
217
+ className={classNames(
218
+ `${classPrefix}-popup__content`,
219
+ {
220
+ [`${classPrefix}-popup__content--arrow`]: showArrow
221
+ },
222
+ overlayInnerClassName
223
+ )}
224
+ style={getOverlayStyle(overlayInnerStyle)}
225
+ onScroll={handleScroll}
226
+ >
227
+ {content}
228
+ {showArrow && (
229
+ <div
230
+ style={styles.arrow}
231
+ className={`${classPrefix}-popup__arrow`}
232
+ {...(hasArrowModifier && { "data-popper-arrow": "" })}
233
+ />
234
+ )}
235
+ </div>
236
+ </div>
237
+ </CSSTransition>
238
+ </Portal>
239
+ </CSSTransition>
240
+ );
241
+ // 使用 useImperativeHandle 暴露给父组件
242
+ // 这样父组件可以通过 ref 获取 popper 实例、popup 元素
243
+ // portal 元素和内容区域元素
244
+ // 以及设置 popup 的显示隐藏状态
245
+ useImperativeHandle(ref, () => ({
246
+ getPopper: () => popperRef.current,
247
+ getPopupElement: () => popupRef.current,
248
+ getPortalElement: () => portalRef.current,
249
+ getPopupContentElement: () => contentRef.current,
250
+ setVisible: (visible: boolean) => onVisibleChange(visible, { trigger: "document" })
251
+ }));
252
+ // 这里使用 React.Fragment 包裹 triggerNode 和 overlay,确保返回一个单一的父节点
253
+ // 这样可以避免在渲染时出现多个根节点的错误
254
+ return (
255
+ <React.Fragment>
256
+ {triggerNode}
257
+ {overlay}
258
+ </React.Fragment>
259
+ );
260
+ });
261
+
262
+ Popup.displayName = "Popup";
263
+
264
+ export default Popup;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
3
+ * */
4
+
5
+ import { TdPopupProps } from "./type";
6
+
7
+ export const popupDefaultProps: TdPopupProps = {
8
+ destroyOnClose: false,
9
+ hideEmptyPopup: false,
10
+ placement: "top",
11
+ showArrow: false,
12
+ trigger: "hover"
13
+ };