@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
package/tag/Tag.tsx ADDED
@@ -0,0 +1,149 @@
1
+ import React, { ForwardRefRenderFunction, FocusEvent, forwardRef, useMemo } from "react";
2
+ import classNames from "classnames";
3
+ import { IconClose } from "@tendaui/icons";
4
+ import tinycolor from "tinycolor2";
5
+ import noop from "../utils/noop";
6
+ import useConfig from "../hooks/useConfig";
7
+ import useGlobalIcon from "../hooks/useGlobalIcon";
8
+ import { StyledProps } from "../common";
9
+ import { TdTagProps } from "./type";
10
+ import { tagDefaultProps } from "./defaultProps";
11
+ import useDefaultProps from "../hooks/useDefaultProps";
12
+
13
+ export interface TagProps extends TdTagProps, StyledProps {
14
+ /**
15
+ * 标签内容
16
+ */
17
+ tabIndex?: number;
18
+ onFocus?: (e: FocusEvent<HTMLDivElement>) => void;
19
+ onBlur?: (e: FocusEvent<HTMLDivElement>) => void;
20
+ }
21
+
22
+ export const TagFunction: ForwardRefRenderFunction<HTMLDivElement, TagProps> = (originalProps, ref) => {
23
+ const props = useDefaultProps<TagProps>(originalProps, tagDefaultProps);
24
+ const {
25
+ theme,
26
+ size,
27
+ shape,
28
+ variant,
29
+ closable,
30
+ maxWidth,
31
+ icon,
32
+ content,
33
+ onClick = noop,
34
+ onClose = noop,
35
+ className,
36
+ style,
37
+ disabled,
38
+ children,
39
+ color,
40
+ title: titleAttr,
41
+ ...otherTagProps
42
+ } = props;
43
+
44
+ const { classPrefix } = useConfig();
45
+ const { CloseIcon } = useGlobalIcon({
46
+ CloseIcon: IconClose
47
+ });
48
+
49
+ const tagClassPrefix = `${classPrefix}-tag`;
50
+
51
+ const sizeMap = {
52
+ large: `${classPrefix}-size-l`,
53
+ small: `${classPrefix}-size-s`
54
+ };
55
+
56
+ const tagClassNames = classNames(
57
+ tagClassPrefix,
58
+ `${tagClassPrefix}--${theme}`,
59
+ `${tagClassPrefix}--${variant}`,
60
+ {
61
+ [`${tagClassPrefix}--${shape}`]: shape !== "square",
62
+ [`${tagClassPrefix}--ellipsis`]: !!maxWidth,
63
+ [`${tagClassPrefix}--disabled`]: disabled
64
+ },
65
+ sizeMap[size],
66
+ className
67
+ );
68
+
69
+ const deleteIcon = (
70
+ <CloseIcon
71
+ onClick={(e) => {
72
+ if (disabled) return;
73
+ onClose({ e });
74
+ }}
75
+ className={`${tagClassPrefix}__icon-close`}
76
+ />
77
+ );
78
+
79
+ const title = useMemo(() => {
80
+ if (Reflect.has(props, "title")) return titleAttr;
81
+ if (children && typeof children === "string") return children;
82
+ if (content && typeof content === "string") return content;
83
+ }, [children, content, props, titleAttr]);
84
+
85
+ const titleAttribute = title ? { title } : undefined;
86
+
87
+ const getTagStyle = useMemo(() => {
88
+ if (!color) return style;
89
+ const luminance = tinycolor(color).getLuminance();
90
+
91
+ const calculatedStyle: React.CSSProperties = {};
92
+
93
+ calculatedStyle.color = luminance > 0.5 ? "black" : "white";
94
+ if (variant === "outline" || variant === "light-outline") {
95
+ calculatedStyle.borderColor = color;
96
+ }
97
+
98
+ if (variant !== "outline") {
99
+ const getLightestShade = () => {
100
+ const { r, g, b } = tinycolor(color).toRgb();
101
+ // alpha 0.1 is designed by @wen1kang
102
+ return `rgba(${r}, ${g}, ${b}, 0.1)`;
103
+ };
104
+
105
+ calculatedStyle.backgroundColor = variant === "dark" ? color : getLightestShade();
106
+ }
107
+ if (variant !== "dark") {
108
+ calculatedStyle.color = color;
109
+ }
110
+ return { ...calculatedStyle, ...style };
111
+ }, [color, variant, style]);
112
+
113
+ const getTextStyle = useMemo(() => {
114
+ if (!maxWidth) return {};
115
+
116
+ return {
117
+ maxWidth: isNaN(Number(maxWidth)) ? String(maxWidth) : `${maxWidth}px`
118
+ };
119
+ }, [maxWidth]);
120
+
121
+ const tag = (
122
+ <div
123
+ ref={ref}
124
+ className={tagClassNames}
125
+ onClick={(e) => {
126
+ if (disabled) return;
127
+ onClick({ e });
128
+ }}
129
+ style={getTagStyle}
130
+ {...otherTagProps}
131
+ >
132
+ <>
133
+ {icon}
134
+ <span className={maxWidth ? `${tagClassPrefix}--text` : undefined} style={getTextStyle} {...titleAttribute}>
135
+ {children ?? content}
136
+ </span>
137
+ {closable && !disabled && deleteIcon}
138
+ </>
139
+ </div>
140
+ );
141
+
142
+ return tag;
143
+ };
144
+
145
+ export const Tag = forwardRef(TagFunction);
146
+
147
+ Tag.displayName = "Tag";
148
+
149
+ export default Tag;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
3
+ * */
4
+
5
+ import { TdTagProps, TdCheckTagProps, TdCheckTagGroupProps } from "./type";
6
+
7
+ export const tagDefaultProps: TdTagProps = {
8
+ closable: false,
9
+ disabled: false,
10
+ icon: undefined,
11
+ shape: "square",
12
+ size: "medium",
13
+ theme: "default",
14
+ variant: "dark"
15
+ };
16
+
17
+ export const checkTagDefaultProps: TdCheckTagProps = { disabled: false, size: "medium" };
18
+
19
+ export const checkTagGroupDefaultProps: TdCheckTagGroupProps = { multiple: false, defaultValue: [] };
package/tag/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { TagFunction } from "./Tag";
2
+ import forwardRefWithStatics from "../utils/forwardRefWithStatics";
3
+ import "./style/index.js";
4
+ export type { TagProps } from "./Tag";
5
+
6
+ export const Tag = forwardRefWithStatics(TagFunction, {});
7
+ Tag.displayName = "Tag";
8
+ export * from "./type";
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/tag/_index.scss";
package/tag/type.ts ADDED
@@ -0,0 +1,170 @@
1
+ import { TNode, TElement, SizeEnum } from "../common";
2
+ import { MouseEvent, KeyboardEvent } from "react";
3
+
4
+ export interface TdTagProps {
5
+ /**
6
+ * 组件子元素,同 `content`
7
+ */
8
+ children?: TNode;
9
+ /**
10
+ * 标签是否可关闭
11
+ * @default false
12
+ */
13
+ closable?: boolean;
14
+ /**
15
+ * 自定义标签颜色
16
+ * @default ''
17
+ */
18
+ color?: string;
19
+ /**
20
+ * 组件子元素
21
+ */
22
+ content?: TNode;
23
+ /**
24
+ * 标签禁用态,失效标签不能触发事件。默认风格(theme=default)才有禁用态
25
+ * @default false
26
+ */
27
+ disabled?: boolean;
28
+ /**
29
+ * 标签中的图标,可自定义图标呈现
30
+ */
31
+ icon?: TElement;
32
+ /**
33
+ * 标签最大宽度,宽度超出后会出现省略号。示例:'50px' / 80
34
+ */
35
+ maxWidth?: string | number;
36
+ /**
37
+ * 标签类型,有三种:方形、圆角方形、标记型
38
+ * @default square
39
+ */
40
+ shape?: "square" | "round" | "mark";
41
+ /**
42
+ * 标签尺寸
43
+ * @default medium
44
+ */
45
+ size?: SizeEnum;
46
+ /**
47
+ * 组件风格,用于描述组件不同的应用场景
48
+ * @default default
49
+ */
50
+ theme?: "default" | "primary" | "warning" | "danger" | "success";
51
+ /**
52
+ * 标签标题,在标签hover时展示,默认为标签内容
53
+ * @default ''
54
+ */
55
+ title?: string;
56
+ /**
57
+ * 标签风格变体
58
+ * @default dark
59
+ */
60
+ variant?: "dark" | "light" | "outline" | "light-outline";
61
+ /**
62
+ * 点击时触发
63
+ */
64
+ onClick?: (context: { e: MouseEvent<HTMLSpanElement> }) => void;
65
+ /**
66
+ * 如果关闭按钮存在,点击关闭按钮时触发
67
+ */
68
+ onClose?: (context: { e: MouseEvent<SVGSVGElement> }) => void;
69
+ }
70
+
71
+ export interface TdCheckTagProps {
72
+ /**
73
+ * 标签选中的状态,默认风格(theme=default)才有选中态
74
+ */
75
+ checked?: boolean;
76
+ /**
77
+ * 标签选中的状态,默认风格(theme=default)才有选中态,非受控属性
78
+ */
79
+ defaultChecked?: boolean;
80
+ /**
81
+ * 透传标签选中态属性
82
+ */
83
+ checkedProps?: TdTagProps;
84
+ /**
85
+ * 组件子元素
86
+ */
87
+ children?: TNode;
88
+ /**
89
+ * 组件子元素;传入数组时:[选中内容,非选中内容]
90
+ */
91
+ content?: [] | TNode;
92
+ /**
93
+ * 标签禁用态,失效标签不能触发事件。默认风格(theme=default)才有禁用态
94
+ * @default false
95
+ */
96
+ disabled?: boolean;
97
+ /**
98
+ * 标签尺寸
99
+ * @default medium
100
+ */
101
+ size?: SizeEnum;
102
+ /**
103
+ * 透传标签未选态属性
104
+ */
105
+ uncheckedProps?: TdTagProps;
106
+ /**
107
+ * 标签唯一标识,一般用于标签组场景,单个可选择标签无需设置
108
+ */
109
+ value?: string | number;
110
+ /**
111
+ * 状态切换时触发
112
+ */
113
+ onChange?: (checked: boolean, context: CheckTagChangeContext) => void;
114
+ /**
115
+ * 点击标签时触发
116
+ */
117
+ onClick?: (context: { e: MouseEvent<HTMLSpanElement> }) => void;
118
+ }
119
+
120
+ export interface TdCheckTagGroupProps {
121
+ /**
122
+ * 透传标签选中态属性
123
+ */
124
+ checkedProps?: TdTagProps;
125
+ /**
126
+ * 是否支持选中多个标签
127
+ * @default false
128
+ */
129
+ multiple?: boolean;
130
+ /**
131
+ * 标签选项列表
132
+ */
133
+ options?: CheckTagGroupOption[];
134
+ /**
135
+ * 透传标签未选态属性
136
+ */
137
+ uncheckedProps?: TdTagProps;
138
+ /**
139
+ * 选中标签值
140
+ * @default []
141
+ */
142
+ value?: CheckTagGroupValue;
143
+ /**
144
+ * 选中标签值,非受控属性
145
+ * @default []
146
+ */
147
+ defaultValue?: CheckTagGroupValue;
148
+ /**
149
+ * null
150
+ */
151
+ onChange?: (value: CheckTagGroupValue, context: CheckTagGroupChangeContext) => void;
152
+ }
153
+
154
+ export interface CheckTagChangeContext {
155
+ e: MouseEvent<HTMLDivElement> | KeyboardEvent<HTMLDivElement>;
156
+ value: string | number;
157
+ }
158
+
159
+ export interface CheckTagGroupOption extends TdCheckTagProps {
160
+ label: string | TNode;
161
+ value: string | number;
162
+ }
163
+
164
+ export type CheckTagGroupValue = Array<string | number>;
165
+
166
+ export interface CheckTagGroupChangeContext {
167
+ type: "check" | "uncheck";
168
+ e: MouseEvent<HTMLDivElement> | KeyboardEvent<HTMLDivElement>;
169
+ value: string | number;
170
+ }
@@ -0,0 +1,215 @@
1
+ import React, { CompositionEvent, KeyboardEvent, useRef, useImperativeHandle, forwardRef, MouseEvent } from "react";
2
+ import { IconClear } from "@tendaui/icons";
3
+ import { isFunction } from "lodash-es";
4
+ import classnames from "classnames";
5
+ import useConfig from "../hooks/useConfig";
6
+ import TInput, { InputValue, InputRef } from "../input";
7
+ import { InputValueChangeContext, TdTagInputProps } from "./type";
8
+ import useTagList from "./hooks/useTagList";
9
+ import useTagScroll from "./hooks/useTagScroll";
10
+ import useHover from "./hooks/useHover";
11
+ import useControlled from "../hooks/useControlled";
12
+ import { StyledProps } from "../common";
13
+ import { tagInputDefaultProps } from "./defaultProps";
14
+ import useDefaultProps from "../hooks/useDefaultProps";
15
+ import useGlobalIcon from "../hooks/useGlobalIcon";
16
+ import useDragSorter from "../hooks/useDragSorter";
17
+
18
+ export interface TagInputProps extends TdTagInputProps, StyledProps {
19
+ options?: unknown[]; // 参数穿透options, 给SelectInput/SelectInput 自定义选中项呈现的内容和多选状态下设置折叠项内容
20
+ }
21
+
22
+ const TagInput = forwardRef<InputRef, TagInputProps>((originalProps, ref) => {
23
+ const props = useDefaultProps<TagInputProps>(originalProps, tagInputDefaultProps);
24
+ const { classPrefix: prefix } = useConfig();
25
+
26
+ const { CloseCircleFilledIcon } = useGlobalIcon({
27
+ CloseCircleFilledIcon: IconClear
28
+ });
29
+
30
+ const {
31
+ excessTagsDisplayType,
32
+ autoWidth,
33
+ borderless,
34
+ readonly,
35
+ disabled,
36
+ clearable,
37
+ placeholder,
38
+ valueDisplay,
39
+ label,
40
+ inputProps,
41
+ size,
42
+ tips,
43
+ status,
44
+ suffixIcon,
45
+ suffix,
46
+ prefixIcon,
47
+ maxRows,
48
+ onClick,
49
+ onPaste,
50
+ onFocus,
51
+ onBlur
52
+ } = props;
53
+
54
+ const [tInputValue, setTInputValue] = useControlled(props, "inputValue", props.onInputChange);
55
+
56
+ const { isHover, addHover, cancelHover } = useHover(props);
57
+ const { getDragProps } = useDragSorter({
58
+ ...props,
59
+ sortOnDraggable: props.dragSort,
60
+ onDragOverCheck: {
61
+ x: true,
62
+ targetClassNameRegExp: new RegExp(`^${prefix}-tag`)
63
+ }
64
+ });
65
+ const isCompositionRef = useRef(false);
66
+
67
+ const { scrollToRight, onWheel, scrollToRightOnEnter, scrollToLeftOnLeave, tagInputRef } = useTagScroll(props);
68
+ // handle tag add and remove
69
+ const { tagValue, onClose, onInnerEnter, onInputBackspaceKeyUp, clearAll, renderLabel, onInputBackspaceKeyDown } =
70
+ useTagList({
71
+ ...props,
72
+ getDragProps
73
+ });
74
+ const NAME_CLASS = `${prefix}-tag-input`;
75
+ const WITH_SUFFIX_ICON_CLASS = `${prefix}-tag-input__with-suffix-icon`;
76
+ const CLEAR_CLASS = `${prefix}-tag-input__suffix-clear ${prefix}-icon`;
77
+ const BREAK_LINE_CLASS = `${prefix}-tag-input--break-line`;
78
+
79
+ const tagInputPlaceholder = !tagValue?.length ? placeholder : "";
80
+
81
+ const showClearIcon = Boolean(!readonly && !disabled && clearable && isHover && tagValue?.length);
82
+
83
+ useImperativeHandle(ref as InputRef, () => ({
84
+ ...(tagInputRef.current || {})
85
+ }));
86
+
87
+ const onInputCompositionstart = (value: InputValue, context: { e: CompositionEvent<HTMLInputElement> }) => {
88
+ isCompositionRef.current = true;
89
+ inputProps?.onCompositionstart?.(value, context);
90
+ };
91
+
92
+ const onInputCompositionend = (value: InputValue, context: { e: CompositionEvent<HTMLInputElement> }) => {
93
+ isCompositionRef.current = false;
94
+ inputProps?.onCompositionend?.(value, context);
95
+ };
96
+
97
+ const onInputEnter = (value: InputValue, context: { e: KeyboardEvent<HTMLInputElement> }) => {
98
+ setTInputValue("", { e: context.e, trigger: "enter" });
99
+ if (!isCompositionRef.current) {
100
+ onInnerEnter(value, context);
101
+ }
102
+ scrollToRight();
103
+ };
104
+
105
+ const onInnerClick = (context: { e: MouseEvent<HTMLDivElement> }) => {
106
+ if (!props.disabled && !props.readonly) {
107
+ tagInputRef.current?.currentElement?.focus?.();
108
+ }
109
+ onClick?.(context);
110
+ };
111
+
112
+ const onClearClick = (e: React.MouseEvent<SVGSVGElement>) => {
113
+ clearAll({ e });
114
+ setTInputValue("", { e, trigger: "clear" });
115
+ props.onClear?.({ e });
116
+ };
117
+
118
+ const suffixIconNode = showClearIcon ? (
119
+ <CloseCircleFilledIcon
120
+ className={CLEAR_CLASS}
121
+ onClick={onClearClick as unknown as React.MouseEventHandler<HTMLSpanElement>}
122
+ />
123
+ ) : (
124
+ suffixIcon
125
+ );
126
+
127
+ // 自定义 Tag 节点
128
+ const displayNode = isFunction(valueDisplay)
129
+ ? valueDisplay({
130
+ value: tagValue,
131
+ onClose: (index) => onClose({ index })
132
+ })
133
+ : valueDisplay;
134
+
135
+ const isEmpty = !(Array.isArray(tagValue) && tagValue.length);
136
+
137
+ const classes = [
138
+ NAME_CLASS,
139
+ {
140
+ [BREAK_LINE_CLASS]: excessTagsDisplayType === "break-line",
141
+ [WITH_SUFFIX_ICON_CLASS]: !!suffixIconNode,
142
+ [`${prefix}-is-empty`]: isEmpty,
143
+ [`${prefix}-tag-input--with-tag`]: !isEmpty,
144
+ [`${prefix}-tag-input--max-rows`]: excessTagsDisplayType === "break-line" && maxRows,
145
+ [`${prefix}-tag-input--drag-sort`]: props.dragSort && !disabled && !readonly
146
+ },
147
+ props.className
148
+ ];
149
+
150
+ const maxRowsStyle = maxRows
151
+ ? ({
152
+ "--max-rows": maxRows,
153
+ "--tag-input-size": size
154
+ } as React.CSSProperties)
155
+ : {};
156
+
157
+ return (
158
+ <TInput
159
+ ref={tagInputRef as unknown as React.Ref<HTMLInputElement>}
160
+ value={tInputValue}
161
+ onChange={(val, context) => {
162
+ setTInputValue(val, { ...context, trigger: "input" } as InputValueChangeContext);
163
+ }}
164
+ autoWidth={true} // 控制input_inner的宽度 设置为true让内部input不会提前换行
165
+ onWheel={onWheel}
166
+ size={size}
167
+ borderless={borderless}
168
+ readonly={readonly}
169
+ disabled={disabled}
170
+ label={renderLabel({ displayNode, label })}
171
+ className={classnames(classes)}
172
+ style={{
173
+ ...props.style,
174
+ ...maxRowsStyle
175
+ }}
176
+ tips={tips}
177
+ status={status}
178
+ placeholder={tagInputPlaceholder}
179
+ suffix={suffix}
180
+ prefixIcon={prefixIcon}
181
+ suffixIcon={suffixIconNode}
182
+ showInput={!inputProps?.readonly || !tagValue || !tagValue?.length}
183
+ keepWrapperWidth={!autoWidth}
184
+ onPaste={onPaste}
185
+ onClick={onInnerClick}
186
+ onEnter={onInputEnter}
187
+ onKeydown={onInputBackspaceKeyDown}
188
+ onKeyup={onInputBackspaceKeyUp}
189
+ onMouseenter={(context) => {
190
+ addHover(context);
191
+ scrollToRightOnEnter();
192
+ }}
193
+ onMouseleave={(context) => {
194
+ cancelHover(context);
195
+ scrollToLeftOnLeave();
196
+ }}
197
+ onFocus={(inputValue, context) => {
198
+ onFocus?.(tagValue, { e: context.e, inputValue });
199
+ }}
200
+ onBlur={(inputValue, context) => {
201
+ if (tInputValue) {
202
+ setTInputValue("", { e: context.e, trigger: "blur" });
203
+ }
204
+ onBlur?.(tagValue, { e: context.e, inputValue: "" });
205
+ }}
206
+ onCompositionstart={onInputCompositionstart}
207
+ onCompositionend={onInputCompositionend}
208
+ {...inputProps}
209
+ />
210
+ );
211
+ });
212
+
213
+ TagInput.displayName = "TagInput";
214
+
215
+ export default TagInput;
@@ -0,0 +1,15 @@
1
+ import { TdTagInputProps } from "./type";
2
+
3
+ export const tagInputDefaultProps: TdTagInputProps = {
4
+ autoWidth: false,
5
+ borderless: false,
6
+ clearable: false,
7
+ dragSort: false,
8
+ excessTagsDisplayType: "break-line",
9
+ defaultInputValue: "",
10
+ minCollapsedNum: 0,
11
+ placeholder: undefined,
12
+ readonly: false,
13
+ size: "medium",
14
+ defaultValue: []
15
+ };
@@ -0,0 +1,28 @@
1
+ import { useState, MouseEvent } from "react";
2
+ import { TdTagInputProps } from "../type";
3
+
4
+ export interface UseHoverParams {
5
+ readonly: boolean;
6
+ disabled: boolean;
7
+ onMouseenter: (context: { e: MouseEvent<HTMLDivElement> }) => void;
8
+ onMouseleave: (context: { e: MouseEvent<HTMLDivElement> }) => void;
9
+ }
10
+
11
+ export default function useHover(props: TdTagInputProps) {
12
+ const { readonly, disabled, onMouseenter, onMouseleave } = props;
13
+ const [isHover, setIsHover] = useState<boolean>(false);
14
+
15
+ const addHover = (context: Parameters<UseHoverParams["onMouseenter"]>[0]) => {
16
+ if (readonly || disabled) return;
17
+ setIsHover(true);
18
+ onMouseenter?.(context);
19
+ };
20
+
21
+ const cancelHover = (context: Parameters<UseHoverParams["onMouseleave"]>[0]) => {
22
+ if (readonly || disabled) return;
23
+ setIsHover(false);
24
+ onMouseleave?.(context);
25
+ };
26
+
27
+ return { isHover, addHover, cancelHover };
28
+ }