@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,36 @@
1
+ import React, { useEffect, FC } from "react";
2
+ import classnames from "classnames";
3
+ import circleAdapter from "./circleAdapter";
4
+ import useDomRefCallback from "../hooks/useDomRefCallback";
5
+ import useConfig from "../hooks/useConfig";
6
+
7
+ /**
8
+ * Loading组件 渐变部分实现
9
+ */
10
+ const GradientLoading: FC = () => {
11
+ const { classPrefix } = useConfig();
12
+ const [conicRef, setConicRef] = useDomRefCallback();
13
+ const gradientClass = `${classPrefix}-loading__gradient`;
14
+
15
+ useEffect(() => {
16
+ const el = conicRef;
17
+ circleAdapter(el);
18
+ }, [conicRef]);
19
+
20
+ return (
21
+ <svg
22
+ className={classnames(gradientClass, `${classPrefix}-icon-loading`)}
23
+ viewBox="0 0 12 12"
24
+ version="1.1"
25
+ width="1em"
26
+ height="1em"
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ >
29
+ <foreignObject x="0" y="0" width="12" height="12">
30
+ <div className={`${gradientClass}-conic`} ref={setConicRef} />
31
+ </foreignObject>
32
+ </svg>
33
+ );
34
+ };
35
+
36
+ export default GradientLoading;
@@ -0,0 +1,169 @@
1
+ import React, { useState, useEffect, useMemo, CSSProperties } from "react";
2
+ import classnames from "classnames";
3
+ import { canUseDocument } from "../utils/dom";
4
+ import useConfig from "../hooks/useConfig";
5
+ import { StyledProps } from "../common";
6
+ import { TdLoadingProps } from "./type";
7
+ import Portal from "../common/Portal";
8
+ import Gradient from "./Gradient";
9
+ import { loadingDefaultProps } from "./defaultProps";
10
+ import useDefaultProps from "../hooks/useDefaultProps";
11
+ import { addClass, removeClass } from "../utils/style";
12
+
13
+ export interface LoadingProps extends TdLoadingProps, StyledProps {}
14
+
15
+ const Loading: React.FC<LoadingProps> = (props) => {
16
+ const {
17
+ attach,
18
+ indicator,
19
+ text,
20
+ loading,
21
+ size,
22
+ delay,
23
+ fullscreen,
24
+ preventScrollThrough,
25
+ showOverlay,
26
+ content,
27
+ children,
28
+ inheritColor,
29
+ zIndex,
30
+ className,
31
+ style
32
+ } = useDefaultProps<LoadingProps>(props, loadingDefaultProps);
33
+
34
+ const [showLoading, setShowLoading] = useState(() => (delay ? false : loading));
35
+
36
+ const { classPrefix } = useConfig();
37
+
38
+ const name = `${classPrefix}-loading`;
39
+ const centerClass = `${classPrefix}-loading--center`;
40
+ const inheritColorClass = `${classPrefix}-loading--inherit-color`;
41
+ const fullClass = `${classPrefix}-loading--full`;
42
+ const fullscreenClass = `${classPrefix}-loading__fullscreen`;
43
+ const lockClass = `${classPrefix}-loading--lock`;
44
+ const overlayClass = `${classPrefix}-loading__overlay`;
45
+ const relativeClass = `${classPrefix}-loading__parent`;
46
+ const textClass = `${classPrefix}-loading__text`;
47
+
48
+ useEffect(() => {
49
+ let timer: NodeJS.Timeout;
50
+
51
+ if (delay && loading) {
52
+ timer = setTimeout(() => {
53
+ setShowLoading(loading);
54
+ }, delay);
55
+ } else {
56
+ // Use setTimeout to avoid calling setState synchronously within an effect
57
+ setTimeout(() => {
58
+ setShowLoading(loading);
59
+ }, 0);
60
+ }
61
+ return () => {
62
+ clearTimeout(timer);
63
+ };
64
+ }, [delay, loading]);
65
+
66
+ const calcStyles = useMemo<React.CSSProperties>(() => {
67
+ const styles: CSSProperties = {};
68
+
69
+ if (zIndex !== undefined) {
70
+ styles.zIndex = zIndex;
71
+ }
72
+
73
+ if (!["small", "medium", "large"].includes(size)) {
74
+ styles.fontSize = size;
75
+ }
76
+
77
+ return styles;
78
+ }, [size, zIndex]);
79
+
80
+ const sizeMap = {
81
+ large: `${classPrefix}-size-l`,
82
+ small: `${classPrefix}-size-s`,
83
+ medium: `${classPrefix}-size-m`
84
+ };
85
+
86
+ const baseClasses = classnames(
87
+ centerClass,
88
+ sizeMap[size],
89
+ {
90
+ [inheritColorClass]: inheritColor
91
+ },
92
+ className
93
+ );
94
+
95
+ useEffect(() => {
96
+ if (preventScrollThrough && fullscreen && canUseDocument && loading) {
97
+ addClass(document.body, lockClass);
98
+ }
99
+ return () => {
100
+ removeClass(document.body, lockClass);
101
+ };
102
+ }, [loading, preventScrollThrough, fullscreen, lockClass]);
103
+
104
+ const commonContent = () => {
105
+ let renderIndicator = <Gradient />;
106
+
107
+ if (indicator && typeof indicator !== "boolean") {
108
+ renderIndicator = indicator as React.ReactElement;
109
+ }
110
+ return (
111
+ <>
112
+ {indicator ? renderIndicator : null}
113
+ {text ? <div className={textClass}>{text}</div> : null}
114
+ </>
115
+ );
116
+ };
117
+
118
+ if (fullscreen) {
119
+ return loading ? (
120
+ <div className={classnames(name, fullscreenClass, centerClass, overlayClass)} style={{ ...calcStyles, ...style }}>
121
+ <div className={baseClasses}>{commonContent()}</div>
122
+ </div>
123
+ ) : null;
124
+ }
125
+
126
+ if (content || children) {
127
+ return (
128
+ <div className={relativeClass} style={style}>
129
+ {content || children}
130
+ {showLoading ? (
131
+ <div
132
+ className={classnames(name, baseClasses, fullClass, {
133
+ [overlayClass]: showOverlay
134
+ })}
135
+ style={calcStyles}
136
+ >
137
+ {commonContent()}
138
+ </div>
139
+ ) : null}
140
+ </div>
141
+ );
142
+ }
143
+ if (attach) {
144
+ return (
145
+ <Portal attach={attach}>
146
+ {loading ? (
147
+ <div
148
+ className={classnames(name, baseClasses, fullClass, {
149
+ [overlayClass]: showOverlay
150
+ })}
151
+ style={{ ...calcStyles, ...style }}
152
+ >
153
+ {commonContent()}
154
+ </div>
155
+ ) : null}
156
+ </Portal>
157
+ );
158
+ }
159
+
160
+ return loading ? (
161
+ <div className={classnames(name, baseClasses)} style={{ ...calcStyles, ...style }}>
162
+ {commonContent()}
163
+ </div>
164
+ ) : null;
165
+ };
166
+
167
+ Loading.displayName = "Loading";
168
+
169
+ export default Loading;
@@ -0,0 +1,44 @@
1
+ import setStyle from "./utils/setStyle";
2
+ import { getIEVersion } from "../utils/helper";
3
+
4
+ export default function circleAdapter(circleElem: HTMLElement) {
5
+ let basicStyle = {};
6
+
7
+ if (!circleElem || typeof window === "undefined") {
8
+ return;
9
+ }
10
+
11
+ const computedStyle = window?.getComputedStyle?.(circleElem);
12
+ const { color, fontSize } = computedStyle || {};
13
+
14
+ // to fix the browser compat of foreignObject in Safari,
15
+ // https://bugs.webkit.org/show_bug.cgi?id=23113
16
+ const ua = window?.navigator?.userAgent || "";
17
+ const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua);
18
+ // 判断是否为 iOS 下的微信和企业微信
19
+ const isIosWechat = /(?=.*iPhone)[?=.*MicroMessenger]/.test(ua) && !/Chrome/.test(ua);
20
+ // 判断是否为 iPadOS 下的微信和企业微信
21
+ const isIpadWechat = /(?=.*iPad)[?=.*MicroMessenger]/.test(ua) && !/Chrome/.test(ua);
22
+
23
+ // 注意:chrome上调试mobile/ipad端时,loading出现异常,属于正常现象,不需要修改。
24
+ if (isSafari || isIosWechat || isIpadWechat) {
25
+ basicStyle = {
26
+ transformOrigin: "0px 0px",
27
+ transform: `scale(${parseInt(fontSize, 10) / 12})`
28
+ };
29
+ }
30
+ // 添加:判断是否为IE浏览器
31
+ if (color && getIEVersion() > 11) {
32
+ const matched = color.match(/[\d.]+/g);
33
+ const endColor = matched ? `rgba(${matched[0]}, ${matched[1]}, ${matched[2]}, 0)` : "";
34
+ setStyle(circleElem, {
35
+ ...basicStyle,
36
+ background: `conic-gradient(from 90deg at 50% 50%,${endColor} 0deg, ${color} 360deg)`
37
+ });
38
+ } else {
39
+ setStyle(circleElem, {
40
+ ...basicStyle,
41
+ background: ""
42
+ });
43
+ }
44
+ }
@@ -0,0 +1,12 @@
1
+ import { TdLoadingProps } from "./type";
2
+
3
+ export const loadingDefaultProps: TdLoadingProps = {
4
+ delay: 0,
5
+ fullscreen: false,
6
+ indicator: true,
7
+ inheritColor: false,
8
+ loading: true,
9
+ preventScrollThrough: true,
10
+ showOverlay: true,
11
+ size: "medium"
12
+ };
@@ -0,0 +1,13 @@
1
+ import _Loading from "./Loading";
2
+ // import { LoadingPlugin as _LoadingPlugin } from './plugin';
3
+
4
+ import "./style/index.js";
5
+
6
+ export type { LoadingProps } from "./Loading";
7
+ export * from "./type";
8
+
9
+ export const Loading = _Loading;
10
+ // export const loading = _LoadingPlugin;
11
+ // export const LoadingPlugin = _LoadingPlugin;
12
+
13
+ export default Loading;
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/loading/_index.scss";
@@ -0,0 +1,71 @@
1
+ import { TNode, AttachNode } from "../common";
2
+
3
+ export interface TdLoadingProps {
4
+ /**
5
+ * 挂载元素,默认挂载到组件本身所在的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body
6
+ * @default ''
7
+ */
8
+ attach?: AttachNode;
9
+ /**
10
+ * 子元素,同 content
11
+ */
12
+ children?: TNode;
13
+ /**
14
+ * 子元素
15
+ */
16
+ content?: TNode;
17
+ /**
18
+ * 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒
19
+ * @default 0
20
+ */
21
+ delay?: number;
22
+ /**
23
+ * 是否显示为全屏加载
24
+ * @default false
25
+ */
26
+ fullscreen?: boolean;
27
+ /**
28
+ * 加载指示符,值为 true 显示默认指示符,值为 false 则不显示,也可以自定义指示符
29
+ * @default true
30
+ */
31
+ indicator?: TNode;
32
+ /**
33
+ * 是否继承父元素颜色
34
+ * @default false
35
+ */
36
+ inheritColor?: boolean;
37
+ /**
38
+ * 是否处于加载状态
39
+ * @default true
40
+ */
41
+ loading?: boolean;
42
+ /**
43
+ * 防止滚动穿透,全屏加载模式有效
44
+ * @default true
45
+ */
46
+ preventScrollThrough?: boolean;
47
+ /**
48
+ * 是否需要遮罩层,遮罩层对包裹元素才有效
49
+ * @default true
50
+ */
51
+ showOverlay?: boolean;
52
+ /**
53
+ * 尺寸,示例:small/medium/large/12px/56px/0.3em
54
+ * @default medium
55
+ */
56
+ size?: string;
57
+ /**
58
+ * 加载提示文案
59
+ */
60
+ text?: TNode;
61
+ /**
62
+ * 消息通知层级,样式默认为 3500
63
+ */
64
+ zIndex?: number;
65
+ }
66
+
67
+ export interface LoadingInstance {
68
+ hide: () => void;
69
+ }
70
+
71
+ export type LoadingMethod = (options: boolean | TdLoadingProps) => LoadingInstance;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 用于为节点增加styles
3
+ * @param el HTMLElement
4
+ * @param style Styles
5
+ */
6
+ function setStyle(el: HTMLElement, styles): void {
7
+ const keys = Object.keys(styles);
8
+ keys.forEach((key) => {
9
+ el.style[key] = styles[key];
10
+ });
11
+ }
12
+
13
+ export default setStyle;
File without changes
@@ -0,0 +1,24 @@
1
+ // notification.ts
2
+ type NotificationType = "default" | "success" | "error" | "info" | "warning";
3
+ type NotificationMessage = {
4
+ id: string;
5
+ type: NotificationType;
6
+ message: string;
7
+ title: string;
8
+ };
9
+
10
+ let addNotification: (notification: NotificationMessage) => void = () => {};
11
+
12
+ export function notification(message: string, type: NotificationType = "default", title: string) {
13
+ const id = new Date().getTime().toString();
14
+ addNotification({
15
+ id,
16
+ message,
17
+ type,
18
+ title
19
+ });
20
+ }
21
+
22
+ export function registerNotificationHandler(cb: typeof addNotification) {
23
+ addNotification = cb;
24
+ }
@@ -0,0 +1,90 @@
1
+ import React, { useCallback, useState } from "react";
2
+ import { TdNotificationProps } from "./type";
3
+ import NotificationItem from "./NotifyItem";
4
+ import useConfig from "../hooks/useConfig";
5
+ export const GAP = 14;
6
+ export const TOAST_WIDTH = 356;
7
+ const NotificationContainer = ({
8
+ notifications,
9
+ onRemove,
10
+ onHoverStart,
11
+ onHoverEnd,
12
+ maxStack,
13
+ position
14
+ }: {
15
+ notifications: TdNotificationProps[];
16
+ onRemove: (id: string) => void;
17
+ onHoverStart: () => void;
18
+ onHoverEnd: () => void;
19
+ maxStack: number;
20
+ position: string;
21
+ }) => {
22
+ const [isHovering, setIsHovering] = useState(false);
23
+ const latestNotifications = notifications.slice(0, maxStack);
24
+ const [y, x] = position.split("-");
25
+ const handleMouseEnter = useCallback(() => {
26
+ setIsHovering(true);
27
+ onHoverStart();
28
+ }, [onHoverStart]);
29
+
30
+ const handleMouseLeave = useCallback(() => {
31
+ setIsHovering(false);
32
+ onHoverEnd();
33
+ }, [onHoverEnd]);
34
+
35
+ const [heights, setHeights] = useState<{ toastId: string; height: number }[]>([]);
36
+
37
+ const { classPrefix: prefix } = useConfig();
38
+
39
+ return (
40
+ <div
41
+ className={`${prefix}-notify`}
42
+ style={
43
+ {
44
+ height: isHovering ? `${notifications.length * 100 + 16}px` : "auto",
45
+ minHeight: "80px",
46
+ "--front-toast-height": (heights[0]?.height || 0) + "px",
47
+ "--toast-width": TOAST_WIDTH + "px"
48
+ } as React.CSSProperties
49
+ }
50
+ data-toaster
51
+ onMouseEnter={handleMouseEnter}
52
+ onMouseLeave={handleMouseLeave}
53
+ data-x-position={x}
54
+ data-y-position={y}
55
+ >
56
+ <div
57
+ className={`${prefix}-notify__container`}
58
+ style={{
59
+ pointerEvents: "all"
60
+ }}
61
+ >
62
+ {latestNotifications.map((notification) => {
63
+ // const stackedStyle = !isHovering && !isLast;
64
+
65
+ // let offsetY = isHovering
66
+ // ? index * 100 // 展开时,索引0在顶部
67
+ // : stackedStyle
68
+ // ? index * 8
69
+ // : 0;
70
+
71
+ // const scale = stackedStyle ? 1 - index * 0.01 : 1;
72
+ // const opacity = stackedStyle ? 1 - index * 0.15 : 1;
73
+ return (
74
+ <NotificationItem
75
+ key={notification.id}
76
+ heights={heights}
77
+ setHeights={setHeights}
78
+ gap={GAP}
79
+ {...notification}
80
+ onRemove={onRemove}
81
+ isExpanded={isHovering}
82
+ />
83
+ );
84
+ })}
85
+ </div>
86
+ </div>
87
+ );
88
+ };
89
+
90
+ export default NotificationContainer;
@@ -0,0 +1,173 @@
1
+ import React, { createContext, useState, useRef, useCallback, useContext } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import NotifyContainer from "./NotifyContainer";
4
+
5
+ // 1. 定义类型
6
+ type NotificationType = "success" | "error" | "warning" | "info";
7
+
8
+ interface Notification {
9
+ id: string;
10
+ type: NotificationType;
11
+ message: string;
12
+ createdAt: number;
13
+ isRemoved: boolean;
14
+ title: string;
15
+ }
16
+
17
+ // 2. 定义 Context 类型
18
+ interface NotificationContextType {
19
+ notify: (type: NotificationType, message: Message) => void;
20
+ success: (message: Message) => void;
21
+ error: (message: Message) => void;
22
+ warning: (message: Message) => void;
23
+ info: (message: Message) => void;
24
+ removeNotification: (id: string) => void;
25
+ }
26
+
27
+ // 3. 创建 Context
28
+ const NotificationContext = createContext<NotificationContextType | null>(null);
29
+
30
+ // 4. 生成唯一 ID 的辅助函数
31
+ const generateId = () => Math.random().toString(36).substr(2, 9);
32
+
33
+ // 5. Provider 组件
34
+ interface NotificationProviderProps {
35
+ children: React.ReactNode;
36
+ maxStack?: number;
37
+ displayDuration?: number;
38
+ position?: string;
39
+ }
40
+
41
+ type Message = {
42
+ title: string;
43
+ message: string;
44
+ };
45
+ export const NotificationProvider: React.FC<NotificationProviderProps> = ({
46
+ children,
47
+ maxStack = 5,
48
+ displayDuration = 3000,
49
+ position = "top-right"
50
+ }) => {
51
+ const [notifications, setNotifications] = useState<Notification[]>([]);
52
+ const timersRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
53
+ const pausedAtRef = useRef<Map<string, number>>(new Map());
54
+
55
+ // 定时器相关函数
56
+ const clearNotificationTimer = useCallback((id: string) => {
57
+ const timer = timersRef.current.get(id);
58
+ if (timer) {
59
+ clearTimeout(timer);
60
+ timersRef.current.delete(id);
61
+ }
62
+ }, []);
63
+
64
+ const startTimer = useCallback(
65
+ (notification: Notification, remainingTime?: number) => {
66
+ const duration = remainingTime ?? displayDuration;
67
+ const timer = setTimeout(() => {
68
+ setNotifications((prev) => prev.map((n) => (n.id === notification.id ? { ...n, isRemoved: true } : n)));
69
+ setTimeout(() => {
70
+ setNotifications((prev) => prev.filter((t) => t.id !== notification.id));
71
+ clearNotificationTimer(notification.id);
72
+ pausedAtRef.current.delete(notification.id);
73
+ }, 400);
74
+ }, duration);
75
+ timersRef.current.set(notification.id, timer);
76
+ },
77
+ [displayDuration, clearNotificationTimer]
78
+ );
79
+
80
+ // 6. 核心通知函数
81
+ const addNotification = useCallback(
82
+ (type: NotificationType, message: Message) => {
83
+ const newNotification: Notification = {
84
+ id: generateId(),
85
+ type,
86
+ title: message.title,
87
+ message: message.message,
88
+ createdAt: Date.now(),
89
+ isRemoved: false
90
+ };
91
+
92
+ setNotifications((prev) => {
93
+ const newNotifications = [newNotification, ...prev];
94
+ const removedNotifications = newNotifications.slice(maxStack);
95
+
96
+ removedNotifications.forEach((notification) => {
97
+ clearNotificationTimer(notification.id);
98
+ pausedAtRef.current.delete(notification.id);
99
+ });
100
+
101
+ return newNotifications.slice(0, maxStack);
102
+ });
103
+
104
+ startTimer(newNotification);
105
+ },
106
+ [maxStack, startTimer, clearNotificationTimer]
107
+ );
108
+
109
+ // 7. 提供的 Context 值
110
+ const contextValue = React.useMemo(
111
+ () => ({
112
+ notify: addNotification,
113
+ success: (message: Message) => addNotification("success", message),
114
+ error: (message: Message) => addNotification("error", message),
115
+ warning: (message: Message) => addNotification("warning", message),
116
+ info: (message: Message) => addNotification("info", message),
117
+ removeNotification: (id: string) => {
118
+ setNotifications((prev) => prev.filter((t) => t.id !== id));
119
+ clearNotificationTimer(id);
120
+ pausedAtRef.current.delete(id);
121
+ }
122
+ }),
123
+ [addNotification, clearNotificationTimer]
124
+ );
125
+
126
+ // 悬停处理
127
+ const clearAllTimers = useCallback(() => {
128
+ const now = Date.now();
129
+ notifications.forEach((notification) => {
130
+ pausedAtRef.current.set(notification.id, now);
131
+ clearNotificationTimer(notification.id);
132
+ });
133
+ }, [notifications, clearNotificationTimer]);
134
+
135
+ const restartAllTimers = useCallback(() => {
136
+ notifications.forEach((notification) => {
137
+ const pausedAt = pausedAtRef.current.get(notification.id);
138
+ if (pausedAt) {
139
+ const elapsedTime = pausedAt - notification.createdAt;
140
+ const remainingTime = Math.max(0, displayDuration - elapsedTime);
141
+ startTimer(notification, remainingTime);
142
+ pausedAtRef.current.delete(notification.id);
143
+ }
144
+ });
145
+ }, [notifications, displayDuration, startTimer]);
146
+
147
+ return (
148
+ <NotificationContext.Provider value={contextValue}>
149
+ {children}
150
+ {createPortal(
151
+ <NotifyContainer
152
+ notifications={notifications as unknown as TdNotificationProps[]}
153
+ onRemove={(id) => contextValue.removeNotification(id)}
154
+ onHoverStart={clearAllTimers}
155
+ onHoverEnd={restartAllTimers}
156
+ data-testid="notification-container"
157
+ maxStack={maxStack}
158
+ position={position}
159
+ />,
160
+ document.body
161
+ )}
162
+ </NotificationContext.Provider>
163
+ );
164
+ };
165
+
166
+ // 8. 创建自定义 Hook
167
+ export const useNotification = () => {
168
+ const context = useContext(NotificationContext);
169
+ if (!context) {
170
+ throw new Error("useNotification must be used within a NotificationProvider");
171
+ }
172
+ return context;
173
+ };