@tendaui/components 1.0.0 → 1.2.3

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 (270) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +176 -176
  3. package/alert/Alert.tsx +3 -2
  4. package/button/_example/base.tsx +10 -0
  5. package/button/_example/icon.tsx +20 -0
  6. package/color-picker/ColorPickPanel.tsx +9 -0
  7. package/color-picker/ColorPicker.tsx +67 -0
  8. package/color-picker/components/panel/alpha.tsx +32 -0
  9. package/color-picker/components/panel/format/index.tsx +47 -0
  10. package/color-picker/components/panel/format/inputs.tsx +119 -0
  11. package/color-picker/components/panel/header.tsx +37 -0
  12. package/color-picker/components/panel/hue.tsx +20 -0
  13. package/color-picker/components/panel/index.tsx +191 -0
  14. package/color-picker/components/panel/saturation.tsx +81 -0
  15. package/color-picker/components/panel/slider.tsx +76 -0
  16. package/color-picker/components/panel/swatches.tsx +84 -0
  17. package/color-picker/components/trigger.tsx +49 -0
  18. package/color-picker/defaultProps.ts +7 -0
  19. package/color-picker/helpers.ts +53 -0
  20. package/color-picker/hooks/useClassNames.ts +9 -0
  21. package/color-picker/hooks/useStyles.ts +39 -0
  22. package/color-picker/index.ts +12 -0
  23. package/color-picker/style/css.js +1 -0
  24. package/color-picker/style/index.js +1 -0
  25. package/color-picker/type.ts +143 -0
  26. package/color-picker/utils/color-picker/cmyk.ts +89 -0
  27. package/color-picker/utils/color-picker/color.ts +467 -0
  28. package/color-picker/utils/color-picker/constants.ts +187 -0
  29. package/color-picker/utils/color-picker/draggable.ts +100 -0
  30. package/color-picker/utils/color-picker/format.ts +95 -0
  31. package/color-picker/utils/color-picker/gradient.ts +243 -0
  32. package/color-picker/utils/color-picker/index.ts +7 -0
  33. package/color-picker/utils/color-picker/types.ts +33 -0
  34. package/common/observe.ts +33 -0
  35. package/common.ts +20 -0
  36. package/config-provider/ConfigContext.tsx +4 -1
  37. package/config-provider/index.ts +1 -1
  38. package/dialog/DialogCard.tsx +4 -6
  39. package/dialog/hooks/useDialogPosition.ts +1 -2
  40. package/dialog/plugin.tsx +3 -2
  41. package/drawer/Drawer.tsx +264 -0
  42. package/drawer/defaultProps.ts +19 -0
  43. package/drawer/hooks/useDrag.ts +98 -0
  44. package/drawer/hooks/useLockStyle.ts +36 -0
  45. package/drawer/index.ts +5 -0
  46. package/drawer/style/css.js +1 -0
  47. package/drawer/style/index.js +1 -0
  48. package/drawer/type.ts +193 -0
  49. package/drawer/utils/index.ts +76 -0
  50. package/fireworks/Fireworks.tsx +138 -0
  51. package/fireworks/index.ts +10 -0
  52. package/fireworks/style/css.js +0 -0
  53. package/fireworks/style/index.js +0 -0
  54. package/fireworks/type.ts +72 -0
  55. package/form/FormItem.tsx +5 -5
  56. package/form/easing.ts +10 -0
  57. package/form/scroll.ts +124 -0
  58. package/form/type.ts +519 -519
  59. package/global-config/default-config.ts +95 -0
  60. package/global-config/locale/ar_KW.ts +270 -0
  61. package/global-config/locale/en_US.ts +280 -0
  62. package/global-config/locale/it_IT.ts +287 -0
  63. package/global-config/locale/ja_JP.ts +279 -0
  64. package/global-config/locale/ko_KR.ts +279 -0
  65. package/global-config/locale/ru_RU.ts +288 -0
  66. package/global-config/locale/zh_CN.ts +279 -0
  67. package/global-config/locale/zh_TW.ts +279 -0
  68. package/global-config/mobile/default-config.ts +6 -0
  69. package/global-config/mobile/locale/ar_KW.ts +113 -0
  70. package/global-config/mobile/locale/en_US.ts +114 -0
  71. package/global-config/mobile/locale/it_IT.ts +114 -0
  72. package/global-config/mobile/locale/ja_JP.ts +101 -0
  73. package/global-config/mobile/locale/ko_KR.ts +101 -0
  74. package/global-config/mobile/locale/ru_RU.ts +113 -0
  75. package/global-config/mobile/locale/zh_CN.ts +101 -0
  76. package/global-config/mobile/locale/zh_TW.ts +101 -0
  77. package/global-config/t.ts +111 -0
  78. package/hooks/useControlled.ts +3 -3
  79. package/hooks/useDeepEffect.ts +32 -0
  80. package/hooks/useGlobalIcon.ts +10 -3
  81. package/hooks/useLastest.ts +2 -6
  82. package/hooks/useResizeObserve.ts +36 -0
  83. package/index.ts +10 -7
  84. package/input/Input.tsx +4 -1
  85. package/input/defaultProps.ts +0 -2
  86. package/input/type.ts +1 -6
  87. package/input-number/InputNumber.tsx +124 -0
  88. package/input-number/defaultProps.ts +17 -0
  89. package/input-number/index.ts +9 -0
  90. package/input-number/style/css.js +1 -0
  91. package/input-number/style/index.js +1 -0
  92. package/input-number/type.ts +147 -0
  93. package/input-number/useInputNumber.tsx +270 -0
  94. package/ip-input/IPInput.tsx +516 -0
  95. package/ip-input/defaultProps.ts +11 -0
  96. package/ip-input/index.ts +3 -0
  97. package/ip-input/style/css.js +1 -0
  98. package/ip-input/style/index.js +1 -0
  99. package/ip-input/type.ts +115 -0
  100. package/ip-input/utils.ts +112 -0
  101. package/layout/Aside.tsx +38 -0
  102. package/layout/Layout.tsx +104 -0
  103. package/layout/defaultProps.ts +9 -0
  104. package/layout/index.ts +9 -0
  105. package/layout/style/css.js +1 -0
  106. package/layout/style/index.js +1 -0
  107. package/layout/type.ts +43 -0
  108. package/list/List.tsx +144 -0
  109. package/list/ListItem.tsx +36 -0
  110. package/list/ListItemMeta.tsx +40 -0
  111. package/list/defaultProps.ts +11 -0
  112. package/list/hooks/useListVirtualScroll.ts +82 -0
  113. package/list/index.ts +11 -0
  114. package/list/style/css.js +1 -0
  115. package/list/style/index.js +1 -0
  116. package/list/type.ts +93 -0
  117. package/locale/LocalReceiver.ts +55 -0
  118. package/locale/ar_KW.ts +7 -0
  119. package/locale/en_US.ts +7 -0
  120. package/locale/it_IT.ts +6 -0
  121. package/locale/ja_JP.ts +6 -0
  122. package/locale/ko_KR.ts +6 -0
  123. package/locale/ru_RU.ts +6 -0
  124. package/locale/zh_CN.ts +5 -0
  125. package/locale/zh_TW.ts +7 -0
  126. package/notification/NotifyContainer.tsx +2 -2
  127. package/notification/NotifyContext.tsx +1 -0
  128. package/package.json +6 -3
  129. package/popup/Popup.tsx +34 -10
  130. package/radio/Radio.tsx +24 -0
  131. package/radio/RadioGroup.tsx +159 -0
  132. package/radio/defaultProps.ts +18 -0
  133. package/radio/index.ts +12 -0
  134. package/radio/style/css.js +0 -0
  135. package/radio/style/index.js +1 -0
  136. package/radio/type.ts +115 -0
  137. package/radio/useKeyboard.ts +36 -0
  138. package/select/hooks/useOptions.ts +10 -7
  139. package/select/hooks/usePanelVirtualScroll.ts +1 -1
  140. package/select/type.ts +382 -382
  141. package/select-input/type.ts +280 -280
  142. package/slider/Slider.tsx +270 -0
  143. package/slider/SliderHandleButton.tsx +50 -0
  144. package/slider/defaultProps.ts +15 -0
  145. package/slider/index.ts +9 -0
  146. package/slider/style/css.js +1 -0
  147. package/slider/style/index.js +1 -0
  148. package/slider/type.ts +77 -0
  149. package/style/all.js +26 -0
  150. package/styles/_global.scss +39 -39
  151. package/styles/_vars.scss +358 -386
  152. package/styles/components/alert/_index.scss +175 -175
  153. package/styles/components/alert/_vars.scss +39 -39
  154. package/styles/components/badge/_index.scss +70 -70
  155. package/styles/components/badge/_vars.scss +25 -25
  156. package/styles/components/button/_index.scss +499 -511
  157. package/styles/components/button/_mixins.scss +39 -39
  158. package/styles/components/button/_vars.scss +120 -122
  159. package/styles/components/checkbox/_index.scss +158 -158
  160. package/styles/components/checkbox/_var.scss +60 -60
  161. package/styles/components/color-picker/_index.scss +586 -0
  162. package/styles/components/color-picker/_mixins.scss +0 -0
  163. package/styles/components/color-picker/_vars.scss +84 -0
  164. package/styles/components/dialog/_animate.scss +135 -135
  165. package/styles/components/dialog/_index.scss +311 -311
  166. package/styles/components/dialog/_vars.scss +59 -59
  167. package/styles/components/drawer/_index.scss +205 -0
  168. package/styles/components/drawer/_mixins.scss +1 -0
  169. package/styles/components/drawer/_var.scss +53 -0
  170. package/styles/components/fireworks/_index.scss +86 -0
  171. package/styles/components/fireworks/_vars.scss +4 -0
  172. package/styles/components/form/_index.scss +174 -174
  173. package/styles/components/form/_mixins.scss +76 -76
  174. package/styles/components/form/_vars.scss +100 -100
  175. package/styles/components/input/_index.scss +349 -349
  176. package/styles/components/input/_mixins.scss +116 -116
  177. package/styles/components/input/_vars.scss +134 -134
  178. package/styles/components/input-number/_index.scss +353 -0
  179. package/styles/components/input-number/_mixins.scss +0 -0
  180. package/styles/components/input-number/_vars.scss +65 -0
  181. package/styles/components/ip-input/_index.scss +280 -0
  182. package/styles/components/layout/_index.scss +47 -0
  183. package/styles/components/layout/_mixin.scss +0 -0
  184. package/styles/components/layout/_vars.scss +18 -0
  185. package/styles/components/layout/doc.scss +74 -0
  186. package/styles/components/list/_index.scss +172 -0
  187. package/styles/components/list/_mixins.scss +0 -0
  188. package/styles/components/list/_vars.scss +41 -0
  189. package/styles/components/loading/_index.scss +112 -112
  190. package/styles/components/loading/_vars.scss +39 -39
  191. package/styles/components/notification/_index.scss +160 -160
  192. package/styles/components/notification/_mixins.scss +12 -12
  193. package/styles/components/notification/_vars.scss +59 -59
  194. package/styles/components/popup/_index.scss +82 -82
  195. package/styles/components/popup/_mixin.scss +149 -149
  196. package/styles/components/popup/_var.scss +31 -31
  197. package/styles/components/radio/_index.scss +376 -0
  198. package/styles/components/radio/_mixins.scss +0 -0
  199. package/styles/components/radio/_var.scss +92 -0
  200. package/styles/components/select/_index.scss +290 -290
  201. package/styles/components/select/_var.scss +65 -65
  202. package/styles/components/select-input/_index.scss +5 -5
  203. package/styles/components/select-input/_var.scss +3 -3
  204. package/styles/components/slider/_index.scss +241 -0
  205. package/styles/components/slider/_mixins.scss +0 -0
  206. package/styles/components/slider/_vars.scss +50 -0
  207. package/styles/components/switch/_index.scss +279 -279
  208. package/styles/components/switch/_vars.scss +61 -61
  209. package/styles/components/table/_index.scss +193 -0
  210. package/styles/components/table/_var.scss +52 -0
  211. package/styles/components/tabs/_index.scss +165 -0
  212. package/styles/components/tabs/_mixins.scss +11 -0
  213. package/styles/components/tabs/_vars.scss +71 -0
  214. package/styles/components/tag/_index.scss +316 -316
  215. package/styles/components/tag/_var.scss +85 -85
  216. package/styles/components/tag-input/_index.scss +163 -163
  217. package/styles/components/tag-input/_vars.scss +16 -16
  218. package/styles/globals.css +250 -250
  219. package/styles/mixins/_focus.scss +7 -7
  220. package/styles/mixins/_layout.scss +32 -32
  221. package/styles/mixins/_reset.scss +10 -10
  222. package/styles/mixins/_scrollbar.scss +31 -31
  223. package/styles/mixins/_text.scss +48 -48
  224. package/styles/rillple.css +16 -16
  225. package/styles/scrollbar.css +41 -41
  226. package/styles/themes/_dark.scss +191 -191
  227. package/styles/themes/_font.scss +69 -79
  228. package/styles/themes/_index.scss +5 -5
  229. package/styles/themes/_light.scss +190 -190
  230. package/styles/themes/_radius.scss +9 -9
  231. package/styles/themes/_size.scss +68 -68
  232. package/styles/themes.css +66 -66
  233. package/styles/utilities/_animation.scss +57 -57
  234. package/styles/utilities/_tips.scss +9 -9
  235. package/tab/TabBar.tsx +85 -0
  236. package/tab/TabNav.tsx +103 -0
  237. package/tab/TabNavItem.tsx +80 -0
  238. package/tab/TabPanel.tsx +42 -0
  239. package/tab/Tabs.tsx +71 -0
  240. package/tab/defaultProps.ts +19 -0
  241. package/tab/index.ts +7 -0
  242. package/tab/style/index.js +1 -0
  243. package/tab/type.ts +125 -0
  244. package/tab/useTabClass.ts +20 -0
  245. package/table/Cell.tsx +109 -0
  246. package/table/TBody.tsx +77 -0
  247. package/table/THead.tsx +63 -0
  248. package/table/TR.tsx +78 -0
  249. package/table/Table.tsx +73 -0
  250. package/table/defaultProps.ts +14 -0
  251. package/table/hooks/index.ts +4 -0
  252. package/table/hooks/useTableClassName.ts +63 -0
  253. package/table/hooks/useTableStyle.ts +93 -0
  254. package/table/index.ts +7 -0
  255. package/table/style/css.js +1 -0
  256. package/table/style/index.js +1 -0
  257. package/table/type.ts +192 -0
  258. package/tag/Tag.tsx +1 -1
  259. package/tag-input/hooks/useTagList.tsx +1 -1
  260. package/utils/dom.ts +4 -0
  261. package/utils/forwardRefWithStatics.ts +1 -4
  262. package/utils/input-number/large-number.ts +423 -0
  263. package/utils/input-number/number.ts +257 -0
  264. package/utils/isFragment.ts +6 -6
  265. package/utils/log/index.ts +3 -0
  266. package/utils/log/log.ts +30 -0
  267. package/utils/log/types.ts +12 -0
  268. package/utils/number.ts +21 -0
  269. package/utils/scroll.ts +26 -0
  270. package/utils/style.ts +2 -4
@@ -0,0 +1,112 @@
1
+ /**
2
+ * IP 地址验证和解析工具函数
3
+ */
4
+
5
+ /**
6
+ * 验证 IPv4 地址段(0-255)
7
+ */
8
+ export function isValidIPv4Segment(segment: string, allowLeadingZeros: boolean = false): boolean {
9
+ if (!segment || segment.trim() === "") return false;
10
+
11
+ // 不允许前导零
12
+ if (!allowLeadingZeros && segment.length > 1 && segment[0] === "0") {
13
+ return false;
14
+ }
15
+
16
+ const num = parseInt(segment, 10);
17
+ return !isNaN(num) && num >= 0 && num <= 255 && num.toString() === segment;
18
+ }
19
+
20
+ /**
21
+ * 验证 IPv4 地址
22
+ */
23
+ export function isValidIPv4(ip: string, allowLeadingZeros: boolean = false): boolean {
24
+ if (!ip || typeof ip !== "string") return false;
25
+
26
+ const segments = ip.split(".");
27
+ if (segments.length !== 4) return false;
28
+
29
+ return segments.every((seg) => isValidIPv4Segment(seg, allowLeadingZeros));
30
+ }
31
+
32
+ /**
33
+ * 验证 CIDR 掩码(IPv4: 0-32, IPv6: 0-128)
34
+ */
35
+ export function isValidCIDRMask(mask: string, isIPv6: boolean = false): boolean {
36
+ if (!mask || typeof mask !== "string") return false;
37
+
38
+ const num = parseInt(mask, 10);
39
+ if (isNaN(num)) return false;
40
+
41
+ const max = isIPv6 ? 128 : 32;
42
+ return num >= 0 && num <= max;
43
+ }
44
+
45
+ /**
46
+ * 验证 IPv6 地址(简化版,使用正则)
47
+ * 支持压缩表示法 :: 和混合 IPv4/IPv6
48
+ */
49
+ export function isValidIPv6(ip: string): boolean {
50
+ if (!ip || typeof ip !== "string") return false;
51
+
52
+ // 简化的 IPv6 验证正则
53
+ // 支持:
54
+ // - 标准格式:2001:0db8:85a3:0000:0000:8a2e:0370:7334
55
+ // - 压缩格式:2001:db8::1
56
+ // - 混合格式:::ffff:192.168.0.1
57
+ const ipv6Regex =
58
+ /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
59
+
60
+ return ipv6Regex.test(ip);
61
+ }
62
+
63
+ /**
64
+ * 从文本中提取 IP 地址
65
+ */
66
+ export function extractIPFromText(text: string): { ip: string; cidr?: string } | null {
67
+ if (!text || typeof text !== "string") return null;
68
+
69
+ // 尝试提取 IPv4
70
+ const ipv4Regex = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\/(\d{1,2}))?/;
71
+ const ipv4Match = text.match(ipv4Regex);
72
+ if (ipv4Match) {
73
+ const ip = ipv4Match[1];
74
+ const cidr = ipv4Match[2];
75
+ if (isValidIPv4(ip, true)) {
76
+ return { ip, cidr };
77
+ }
78
+ }
79
+
80
+ // 尝试提取 IPv6
81
+ const ipv6Regex = /([0-9a-fA-F:]+(?::[0-9a-fA-F:]+)*|::)(?:\/(\d{1,3}))?/;
82
+ const ipv6Match = text.match(ipv6Regex);
83
+ if (ipv6Match) {
84
+ const ip = ipv6Match[1];
85
+ const cidr = ipv6Match[2];
86
+ if (isValidIPv6(ip)) {
87
+ return { ip, cidr };
88
+ }
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ /**
95
+ * 解析 IP 地址为段数组(IPv4)
96
+ */
97
+ export function parseIPv4ToSegments(ip: string): string[] {
98
+ if (!ip) return ["", "", "", ""];
99
+ const segments = ip.split(".");
100
+ return [segments[0] || "", segments[1] || "", segments[2] || "", segments[3] || ""];
101
+ }
102
+
103
+ /**
104
+ * 将段数组组合为 IP 地址(IPv4)
105
+ */
106
+ export function segmentsToIPv4(segments: string[]): string {
107
+ // 如果所有段都为空,返回空字符串
108
+ if (segments.every((seg) => !seg || seg.trim() === "")) {
109
+ return "";
110
+ }
111
+ return segments.join(".");
112
+ }
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import useConfig from "../hooks/useConfig";
4
+ import { TdAsideProps } from "./type";
5
+ import { StyledProps } from "../common";
6
+ import useDefaultProps from "../hooks/useDefaultProps";
7
+ export interface AsideProps extends TdAsideProps, StyledProps {
8
+ /**
9
+ * 文本内容
10
+ */
11
+ children?: React.ReactNode;
12
+ }
13
+
14
+ const Aside: React.FC<AsideProps> = (props) => {
15
+ const { width, className, style, children, ...otherAsideProps } = useDefaultProps(props, { width: "232px" });
16
+
17
+ const { classPrefix } = useConfig();
18
+ const asideClassNames = classNames(`${classPrefix}-layout__sider`, className);
19
+ const asideWidth: string = typeof width === "number" ? `${width}px` : width;
20
+
21
+ const asideStyle: React.CSSProperties = {
22
+ width: asideWidth,
23
+ maxWidth: asideWidth,
24
+ minWidth: asideWidth,
25
+ flex: `0 0 ${asideWidth}`,
26
+ ...style
27
+ };
28
+
29
+ return (
30
+ <aside className={asideClassNames} style={asideStyle} {...otherAsideProps}>
31
+ {children}
32
+ </aside>
33
+ );
34
+ };
35
+
36
+ Aside.displayName = "Aside";
37
+
38
+ export default Aside;
@@ -0,0 +1,104 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+ import useConfig from "../hooks/useConfig";
4
+ import { StyledProps } from "../common";
5
+ import { TdLayoutProps, TdHeaderProps, TdFooterProps } from "./type";
6
+ import { useMemo } from "react";
7
+ import Aside from "./Aside";
8
+ import parseTNode from "../utils/parseTNode";
9
+ export interface LayoutProps extends TdLayoutProps, StyledProps, React.HTMLAttributes<HTMLDivElement> {
10
+ children?: React.ReactNode;
11
+ }
12
+ export interface HeaderProps extends TdHeaderProps, StyledProps, React.HTMLAttributes<HTMLElement> {
13
+ children?: React.ReactNode;
14
+ }
15
+ export interface ContentProps extends StyledProps, React.HTMLAttributes<HTMLElement> {
16
+ children?: React.ReactNode;
17
+ }
18
+ export interface FooterProps extends TdFooterProps, StyledProps, React.HTMLAttributes<HTMLElement> {
19
+ children?: React.ReactNode;
20
+ }
21
+
22
+ const Header: React.FC<HeaderProps> = (props) => {
23
+ const { classPrefix } = useConfig();
24
+ const { className, style = {}, children, height, ...others } = props;
25
+ const renderHeight = isNaN(Number(height)) ? height : `${height}px`;
26
+ const headerClassNames = classNames(`${classPrefix}-layout__header`, className);
27
+ return (
28
+ <header className={headerClassNames} style={{ height: renderHeight, ...style }} {...others}>
29
+ {parseTNode(children)}
30
+ </header>
31
+ );
32
+ };
33
+
34
+ const Footer: React.FC<FooterProps> = (props) => {
35
+ const { classPrefix } = useConfig();
36
+ const { className, style = {}, children, height, ...others } = props;
37
+ const renderHeight = isNaN(Number(height)) ? height : `${height}px`;
38
+ const footerClassNames = classNames(`${classPrefix}-layout__footer`, className);
39
+ return (
40
+ <footer className={footerClassNames} style={{ height: renderHeight, ...style }} {...others}>
41
+ {parseTNode(children)}
42
+ </footer>
43
+ );
44
+ };
45
+ const Content: React.FC<ContentProps> = (props) => {
46
+ const { classPrefix } = useConfig();
47
+ const { className, style, children, content, ...others } = props;
48
+ const contentClassNames = classNames(`${classPrefix}-layout__content`, className);
49
+ return (
50
+ <main className={contentClassNames} style={style} {...others}>
51
+ {parseTNode(content) || parseTNode(children)}
52
+ </main>
53
+ );
54
+ };
55
+
56
+ const Layout: React.FC<LayoutProps> & {
57
+ Header: typeof Header;
58
+ Content: typeof Content;
59
+ Footer: typeof Footer;
60
+ Aside: typeof Aside;
61
+ } = (props) => {
62
+ const { direction, className, style, children, ...otherLayoutProps } = props;
63
+
64
+ const shouldAsides = useMemo(() => {
65
+ const asides: React.ReactElement[] = [];
66
+ React.Children.forEach(children, (child: React.ReactElement) => {
67
+ if (!child || typeof child !== "object") {
68
+ return;
69
+ }
70
+ if (child.type === Aside) {
71
+ asides.push(child);
72
+ }
73
+ });
74
+ return !!asides.length;
75
+ }, [children]);
76
+
77
+ const { classPrefix } = useConfig();
78
+ const layoutClassNames = classNames(
79
+ `${classPrefix}-layout`,
80
+ {
81
+ [`${classPrefix}-layout--with-sider`]: shouldAsides,
82
+ [`${classPrefix}-layout__direction-${direction}`]: direction
83
+ },
84
+ className
85
+ );
86
+
87
+ return (
88
+ <div className={layoutClassNames} style={style} {...otherLayoutProps}>
89
+ {parseTNode(children)}
90
+ </div>
91
+ );
92
+ };
93
+
94
+ Layout.Header = Header;
95
+ Layout.Content = Content;
96
+ Layout.Footer = Footer;
97
+ Layout.Aside = Aside;
98
+
99
+ Header.displayName = "Header";
100
+ Content.displayName = "Content";
101
+ Footer.displayName = "Footer";
102
+ Layout.displayName = "Layout";
103
+
104
+ export default Layout;
@@ -0,0 +1,9 @@
1
+ import { TdAsideProps, TdFooterProps, TdHeaderProps, TdLayoutProps } from "./type";
2
+
3
+ export const asideDefaultProps: TdAsideProps = {};
4
+
5
+ export const footerDefaultProps: TdFooterProps = {};
6
+
7
+ export const headerDefaultProps: TdHeaderProps = {};
8
+
9
+ export const layoutDefaultProps: TdLayoutProps = {};
@@ -0,0 +1,9 @@
1
+ import _Layout from "./Layout";
2
+
3
+ import "./style/index.js";
4
+
5
+ export type { LayoutProps } from "./Layout";
6
+ export * from "./type";
7
+
8
+ export const Layout = _Layout;
9
+ export default Layout;
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/layout/_index.scss";
package/layout/type.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { TNode } from "../common";
2
+
3
+ export interface TdLayoutProps {
4
+ /**
5
+ * 【开发中】布局方向
6
+ */
7
+ direction?: "vertical" | "horizontal";
8
+ }
9
+
10
+ export interface TdHeaderProps {
11
+ /**
12
+ * 顶栏高度。样式表(class)中定义的默认高度为:64px
13
+ * @default ''
14
+ */
15
+ height?: string;
16
+ }
17
+
18
+ export interface TdAsideProps {
19
+ /**
20
+ * 侧边栏宽度。样式表(class)中定义的默认宽度为:232px
21
+ * @default ''
22
+ */
23
+ width?: string;
24
+ }
25
+
26
+ export interface TdContentProps {
27
+ /**
28
+ * 内容,同 content
29
+ */
30
+ children?: TNode;
31
+ /**
32
+ * 内容
33
+ */
34
+ content?: TNode;
35
+ }
36
+
37
+ export interface TdFooterProps {
38
+ /**
39
+ * 底栏高度。样式表(class)中定义的默认高度为:24px
40
+ * @default ''
41
+ */
42
+ height?: string;
43
+ }
package/list/List.tsx ADDED
@@ -0,0 +1,144 @@
1
+ import React, { useImperativeHandle, useMemo, useRef, WheelEvent } from "react";
2
+ import classNames from "classnames";
3
+ import { compact, isString } from "lodash-es";
4
+ import forwardRefWithStatics from "../utils/forwardRefWithStatics";
5
+ import noop from "../utils/noop";
6
+ import parseTNode from "../utils/parseTNode";
7
+ import useConfig from "../hooks/useConfig";
8
+ import useDefaultProps from "../hooks/useDefaultProps";
9
+ import Loading from "../loading";
10
+ import { useLocaleReceiver } from "../locale/LocalReceiver";
11
+ import ListItem from "./ListItem";
12
+ import ListItemMeta from "./ListItemMeta";
13
+ import { listDefaultProps } from "./defaultProps";
14
+ import { useListVirtualScroll } from "./hooks/useListVirtualScroll";
15
+
16
+ import type { StyledProps } from "../common";
17
+ import type { ListInstanceFunctions, TdListProps } from "./type";
18
+
19
+ export interface ListProps extends TdListProps, StyledProps {
20
+ /**
21
+ * 文本内容
22
+ */
23
+ children?: React.ReactNode;
24
+ }
25
+
26
+ /**
27
+ * 列表组件
28
+ */
29
+ const List = forwardRefWithStatics(
30
+ (props: ListProps, ref: React.Ref<ListInstanceFunctions>) => {
31
+ const {
32
+ header,
33
+ footer,
34
+ asyncLoading,
35
+ size,
36
+ split,
37
+ stripe,
38
+ layout,
39
+ children,
40
+ className,
41
+ onLoadMore = noop,
42
+ onScroll = noop,
43
+ style,
44
+ scroll
45
+ } = useDefaultProps<ListProps>(props, listDefaultProps);
46
+ const wrapperRef = useRef<HTMLDivElement>(null);
47
+
48
+ const { classPrefix } = useConfig();
49
+ const [local, t] = useLocaleReceiver("list");
50
+
51
+ const listItems = useMemo(
52
+ () => compact(React.Children.map(children, (child: React.ReactElement) => child?.props)) ?? [],
53
+ [children]
54
+ );
55
+
56
+ const { virtualConfig, cursorStyle, listStyle, isVirtualScroll, onInnerVirtualScroll, scrollToElement } =
57
+ useListVirtualScroll(scroll, wrapperRef, listItems);
58
+
59
+ const COMPONENT_NAME = `${classPrefix}-list`;
60
+
61
+ const handleClickLoad = (e: React.MouseEvent<HTMLDivElement>) => {
62
+ if (asyncLoading === "load-more") {
63
+ onLoadMore({ e });
64
+ }
65
+ };
66
+
67
+ const handleScroll = (event: WheelEvent<HTMLDivElement>): void => {
68
+ const { currentTarget } = event;
69
+ const { scrollTop, offsetHeight, scrollHeight } = currentTarget;
70
+ const scrollBottom = scrollHeight - scrollTop - offsetHeight;
71
+ if (isVirtualScroll) onInnerVirtualScroll(event as unknown as globalThis.WheelEvent);
72
+ onScroll({ e: event, scrollTop, scrollBottom });
73
+ };
74
+
75
+ const loadElement = isString(asyncLoading) ? (
76
+ <div
77
+ className={classNames(`${classPrefix}-list__load`, {
78
+ [`${classPrefix}-list__load--loading`]: asyncLoading === "loading",
79
+ [`${classPrefix}-list__load--load-more`]: asyncLoading === "load-more"
80
+ })}
81
+ onClick={handleClickLoad}
82
+ >
83
+ {asyncLoading === "loading" && (
84
+ <div>
85
+ <Loading loading={true} />
86
+ <span>{t(local.loadingText)}</span>
87
+ </div>
88
+ )}
89
+ {asyncLoading === "load-more" && <span>{t(local.loadingMoreText)}</span>}
90
+ </div>
91
+ ) : (
92
+ asyncLoading
93
+ );
94
+
95
+ useImperativeHandle(ref, () => ({
96
+ scrollTo: scrollToElement
97
+ }));
98
+
99
+ const renderContent = () => (
100
+ <>
101
+ {isVirtualScroll ? (
102
+ <>
103
+ <div style={cursorStyle}></div>
104
+ <ul className={`${COMPONENT_NAME}__inner`} style={listStyle}>
105
+ {virtualConfig.visibleData.map((item, index) => (
106
+ <ListItem key={index} {...(item as any)} />
107
+ ))}
108
+ </ul>
109
+ </>
110
+ ) : (
111
+ <ul className={`${COMPONENT_NAME}__inner`}>{children}</ul>
112
+ )}
113
+ </>
114
+ );
115
+
116
+ return (
117
+ <div
118
+ ref={wrapperRef}
119
+ style={{
120
+ ...style,
121
+ position: isVirtualScroll ? "relative" : undefined
122
+ }}
123
+ onScroll={handleScroll}
124
+ className={classNames(`${COMPONENT_NAME}`, className, {
125
+ [`${COMPONENT_NAME}--split`]: split,
126
+ [`${COMPONENT_NAME}--stripe`]: stripe,
127
+ [`${COMPONENT_NAME}--vertical-action`]: layout === "vertical",
128
+ [`${classPrefix}-size-s`]: size === "small",
129
+ [`${classPrefix}-size-l`]: size === "large"
130
+ })}
131
+ >
132
+ {header && <div className={`${COMPONENT_NAME}__header`}>{parseTNode(header)}</div>}
133
+ {renderContent()}
134
+ {asyncLoading && loadElement}
135
+ {footer && <div className={`${COMPONENT_NAME}__footer`}>{parseTNode(footer)}</div>}
136
+ </div>
137
+ );
138
+ },
139
+ { ListItem, ListItemMeta }
140
+ );
141
+
142
+ List.displayName = "List";
143
+
144
+ export default List;
@@ -0,0 +1,36 @@
1
+ import React, { forwardRef } from "react";
2
+ import classNames from "classnames";
3
+ import useConfig from "../hooks/useConfig";
4
+ import { TdListItemProps } from "./type";
5
+ import { StyledProps } from "../common";
6
+
7
+ export interface ListItemProps extends TdListItemProps, StyledProps {
8
+ /**
9
+ * 文本内容
10
+ */
11
+ children?: React.ReactNode;
12
+ content?: React.ReactNode;
13
+ }
14
+
15
+ /**
16
+ * 列表组件
17
+ */
18
+ const ListItem = forwardRef<HTMLLIElement, ListItemProps>((props, ref) => {
19
+ const { children, className, style, action, content } = props;
20
+ const { classPrefix } = useConfig();
21
+
22
+ const actionElement = action && <ul className={`${classPrefix}-list-item__action`}>{action}</ul>;
23
+
24
+ return (
25
+ <li ref={ref} className={classNames(`${classPrefix}-list-item`, className)} style={style}>
26
+ <div className={`${classPrefix}-list-item-main`}>
27
+ {children ? children : content}
28
+ {actionElement}
29
+ </div>
30
+ </li>
31
+ );
32
+ });
33
+
34
+ ListItem.displayName = "ListItem";
35
+
36
+ export default ListItem;
@@ -0,0 +1,40 @@
1
+ import React, { forwardRef } from "react";
2
+ import classNames from "classnames";
3
+ import useConfig from "../hooks/useConfig";
4
+ import { TdListItemMetaProps } from "./type";
5
+ import { StyledProps } from "../common";
6
+
7
+ export interface ListItemMetaProps extends TdListItemMetaProps, StyledProps {}
8
+ /**
9
+ * 列表组件
10
+ */
11
+ const ListItemMeta = forwardRef<HTMLDivElement, ListItemMetaProps>((props, ref) => {
12
+ const { title, image, description, className, style } = props;
13
+ const { classPrefix } = useConfig();
14
+
15
+ const renderAvatar = () => {
16
+ if (image && typeof image === "string") {
17
+ return (
18
+ <div className={`${classPrefix}-list-item__meta-avatar`}>
19
+ <img src={image} alt="" />
20
+ </div>
21
+ );
22
+ }
23
+ return <div className={`${classPrefix}-list-item__meta-avatar`}>{image}</div>;
24
+ };
25
+ return (
26
+ <div ref={ref} className={classNames(`${classPrefix}-list-item__meta`, className)} style={style}>
27
+ {image && renderAvatar()}
28
+ <div className={`${classPrefix}-list-item__meta-content`}>
29
+ <h3 className={`${classPrefix}-list-item__meta-title`}>{title}</h3>
30
+ <div className={`${classPrefix}-list-item__meta-description`}>
31
+ {typeof description === "string" ? <p>{description}</p> : description}
32
+ </div>
33
+ </div>
34
+ </div>
35
+ );
36
+ });
37
+
38
+ ListItemMeta.displayName = "ListItemMeta";
39
+
40
+ export default ListItemMeta;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
3
+ * */
4
+
5
+ import { TdListProps, TdListItemProps, TdListItemMetaProps } from "./type";
6
+
7
+ export const listDefaultProps: TdListProps = { layout: "horizontal", size: "medium", split: false, stripe: false };
8
+
9
+ export const listItemDefaultProps: TdListItemProps = {};
10
+
11
+ export const listItemMetaDefaultProps: TdListItemMetaProps = {};
@@ -0,0 +1,82 @@
1
+ import React, { useMemo } from "react";
2
+ // import log from "@tdesign/common-js/log/index";
3
+ import useVirtualScroll from "../../hooks/useVirtualScroll";
4
+ import { TdListProps } from "../type";
5
+ import { Styles, type ScrollToElementParams } from "../../common";
6
+ const log = console.log;
7
+ export const useListVirtualScroll = (
8
+ scroll: TdListProps["scroll"],
9
+ listRef: React.MutableRefObject<HTMLElement>,
10
+ listItems: any[]
11
+ ) => {
12
+ const virtualScrollParams = useMemo(
13
+ () => ({
14
+ data: listItems,
15
+ scroll
16
+ }),
17
+ [listItems, scroll]
18
+ );
19
+ const virtualConfig = useVirtualScroll(listRef, virtualScrollParams);
20
+ const { isVirtualScroll } = virtualConfig;
21
+ let lastScrollY = -1;
22
+
23
+ const onInnerVirtualScroll = (e: WheelEvent) => {
24
+ const target = (e.target || e.srcElement) as HTMLElement;
25
+ const top = target.scrollTop;
26
+ if (lastScrollY !== top) {
27
+ virtualConfig.isVirtualScroll && virtualConfig.handleScroll();
28
+ } else {
29
+ lastScrollY = -1;
30
+ }
31
+ lastScrollY = top;
32
+ };
33
+
34
+ const cursorStyle = useMemo(
35
+ () =>
36
+ ({
37
+ position: "absolute",
38
+ width: "1px",
39
+ height: "1px",
40
+ transition: "transform 0.2s",
41
+ transform: `translate(0, ${virtualConfig.scrollHeight}px)`,
42
+ msTransform: `translate(0, ${virtualConfig.scrollHeight}px)`,
43
+ MozTransform: `translate(0, ${virtualConfig.scrollHeight}px)`,
44
+ WebkitTransform: `translate(0, ${virtualConfig.scrollHeight}px)`
45
+ }) as Styles,
46
+ [virtualConfig.scrollHeight]
47
+ );
48
+
49
+ const listStyle = useMemo(
50
+ () =>
51
+ ({
52
+ transform: `translate(0, ${virtualConfig.translateY}px)`,
53
+ msTransform: `translate(0, ${virtualConfig.translateY}px)`,
54
+ MozTransform: `translate(0, ${virtualConfig.translateY}px)`,
55
+ WebkitTransform: `translate(0, ${virtualConfig.translateY}px)`
56
+ }) as Styles,
57
+ [virtualConfig.translateY]
58
+ );
59
+
60
+ const handleScrollTo = (params: ScrollToElementParams) => {
61
+ const { index, key } = params;
62
+ const targetIndex = index === 0 ? index : index ?? Number(key);
63
+ if (!targetIndex && targetIndex !== 0) {
64
+ console.error("List", "scrollTo: `index` or `key` must exist.");
65
+ return;
66
+ }
67
+ if (targetIndex < 0 || targetIndex >= listItems.length) {
68
+ console.error("List", `${targetIndex} does not exist in data, check \`index\` or \`key\` please.`);
69
+ return;
70
+ }
71
+ virtualConfig.scrollToElement({ ...params, index: targetIndex - 1 });
72
+ };
73
+
74
+ return {
75
+ virtualConfig,
76
+ cursorStyle,
77
+ listStyle,
78
+ isVirtualScroll,
79
+ onInnerVirtualScroll,
80
+ scrollToElement: handleScrollTo
81
+ };
82
+ };
package/list/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import _List from "./List";
2
+
3
+ import "./style/index.js";
4
+
5
+ export type { ListProps } from "./List";
6
+ export type { ListItemProps } from "./ListItem";
7
+ export type { ListItemMetaProps } from "./ListItemMeta";
8
+ export * from "./type";
9
+
10
+ export const List = _List;
11
+ export default List;
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/list/_index.scss";