@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,256 @@
1
+ import { ReactElement } from "react";
2
+ import { isPlainObject, get } from "lodash-es";
3
+ import OptionGroup from "../OptionGroup";
4
+ import Option from "../Option";
5
+
6
+ import { SelectValue, TdOptionProps, SelectKeysType, TdSelectProps, SelectOption, SelectOptionGroup } from "../type";
7
+
8
+ type SelectLabeledValue = Required<Omit<TdOptionProps, "disabled">>;
9
+
10
+ export function isSelectOptionGroup(option: SelectOption): option is SelectOptionGroup {
11
+ return !!option && "group" in option && "children" in option;
12
+ }
13
+
14
+ export type ValueToOption = {
15
+ [value: string | number]: TdOptionProps;
16
+ };
17
+
18
+ function setValueToOptionFormOptionDom(dom: ReactElement, valueToOption: ValueToOption, keys: SelectKeysType) {
19
+ const { value, label, children } = dom.props;
20
+
21
+ valueToOption[value] = {
22
+ ...dom.props,
23
+ [keys?.value || "value"]: value,
24
+ [keys?.label || "label"]: label || children || value
25
+ };
26
+ }
27
+
28
+ // 获取 value => option,用于快速基于 value 找到对应的 option
29
+ export const getValueToOption = (
30
+ children: ReactElement,
31
+ options: SelectOption[],
32
+ keys: SelectKeysType
33
+ ): ValueToOption => {
34
+ const valueToOption = {};
35
+
36
+ // options 优先级高于 children
37
+ if (Array.isArray(options)) {
38
+ options.forEach((option) => {
39
+ if (isSelectOptionGroup(option)) {
40
+ option.children?.forEach((child) => {
41
+ valueToOption[get(child, keys?.value || "value")] = {
42
+ ...child,
43
+ value: get(child, keys?.value || "value"),
44
+ label: get(child, keys?.label || "label")
45
+ };
46
+ });
47
+ } else {
48
+ valueToOption[get(option, keys?.value || "value")] = {
49
+ ...(option as object),
50
+ value: get(option, keys?.value || "value"),
51
+ label: get(option, keys?.label || "label")
52
+ } as never;
53
+ }
54
+ });
55
+ return valueToOption;
56
+ }
57
+
58
+ if (isPlainObject(children)) {
59
+ if (children.type === Option) {
60
+ setValueToOptionFormOptionDom(children, valueToOption, keys);
61
+ return valueToOption;
62
+ }
63
+
64
+ if (children.type === OptionGroup) {
65
+ const groupChildren = children.props.children;
66
+
67
+ if (Array.isArray(groupChildren)) {
68
+ groupChildren.forEach((item) => {
69
+ setValueToOptionFormOptionDom(item, valueToOption, keys);
70
+ });
71
+ return valueToOption;
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * children如果存在ReactElement和map函数混写的情况,会出现嵌套数组
78
+ */
79
+ if (Array.isArray(children)) {
80
+ const handlerElement = (item: ReactElement) => {
81
+ if (item.type === Option) {
82
+ setValueToOptionFormOptionDom(item, valueToOption, keys);
83
+ }
84
+
85
+ if (item.type === OptionGroup) {
86
+ const groupChildren = (item.props as SelectOption).children;
87
+ if (Array.isArray(groupChildren)) {
88
+ groupChildren.forEach((groupItem) => {
89
+ setValueToOptionFormOptionDom(groupItem, valueToOption, keys);
90
+ });
91
+ }
92
+ }
93
+
94
+ if (Array.isArray(item)) {
95
+ item.forEach((child) => {
96
+ handlerElement(child);
97
+ });
98
+ }
99
+ };
100
+ children.forEach((item: ReactElement) => handlerElement(item));
101
+ }
102
+
103
+ return valueToOption;
104
+ };
105
+
106
+ // 获取单选的 label
107
+ export const getLabel = (
108
+ children: ReactElement,
109
+ value: SelectValue<TdOptionProps>,
110
+ options: TdOptionProps[],
111
+ keys: SelectKeysType
112
+ ) => {
113
+ let selectedLabel = "";
114
+ // 处理带 options 属性的情况
115
+ if (Array.isArray(options)) {
116
+ options.some((option) => {
117
+ if ([get(value, keys?.value || "value"), value].includes(option.value)) {
118
+ selectedLabel = option.label;
119
+ return true;
120
+ }
121
+ return false;
122
+ });
123
+
124
+ return selectedLabel;
125
+ }
126
+
127
+ if (isPlainObject(children)) {
128
+ selectedLabel = children.props.label;
129
+
130
+ if (children.type === OptionGroup) {
131
+ const groupChildren = children.props.children;
132
+
133
+ if (Array.isArray(groupChildren)) {
134
+ groupChildren.some((item) => {
135
+ const selectedValue = isPlainObject(value) ? get(value, "value") : value;
136
+ if (isPlainObject(item.props) && item.props.value === selectedValue) {
137
+ selectedLabel = item.props.label || item.props.children;
138
+ return true;
139
+ }
140
+ return false;
141
+ });
142
+ }
143
+ }
144
+ }
145
+
146
+ if (Array.isArray(children)) {
147
+ children.some((item: ReactElement) => {
148
+ // 处理分组
149
+ if (item.type === OptionGroup) {
150
+ const groupChildren = item.props.children;
151
+ if (Array.isArray(groupChildren)) {
152
+ const isSelected = groupChildren.some((item) => {
153
+ const selectedValue = isPlainObject(value) ? get(value, "value") : value;
154
+ if (isPlainObject(item.props) && item.props.value === selectedValue) {
155
+ selectedLabel = item.props.label || item.props.children;
156
+ return true;
157
+ }
158
+ return false;
159
+ });
160
+ return isSelected;
161
+ }
162
+ }
163
+ const selectedValue = isPlainObject(value) ? get(value, "value") : value;
164
+ if (isPlainObject(item.props) && item.props.value === selectedValue) {
165
+ selectedLabel = item.props.label || item.props.children;
166
+ return true;
167
+ }
168
+ return false;
169
+ });
170
+ }
171
+
172
+ return selectedLabel;
173
+ };
174
+
175
+ export const getMultipleTags = (values: SelectValue[], keys: SelectKeysType) => {
176
+ const tags = values.map((item) => ({
177
+ label: get(item, keys?.label || "label") || item.toString(),
178
+ value: get(item, keys?.value || "value") || item
179
+ }));
180
+ return tags;
181
+ };
182
+
183
+ export const getSelectValueArr = (
184
+ values: SelectValue | SelectValue[],
185
+ activeValue: SelectValue,
186
+ selected?: boolean,
187
+ valueType?: TdSelectProps["valueType"],
188
+ keys?: SelectKeysType,
189
+ objVal?: SelectValue
190
+ ) => {
191
+ values = Array.isArray(values) ? values : [];
192
+
193
+ if (Array.isArray(values)) {
194
+ let currentValues = [...values];
195
+ const isValueObj = valueType === "object";
196
+ if (selected) {
197
+ currentValues = currentValues.filter((item: SelectLabeledValue) => {
198
+ if (isValueObj) {
199
+ if (isPlainObject(activeValue)) {
200
+ return get(item, keys?.value || "value") !== get(activeValue, keys?.value || "value");
201
+ }
202
+ return get(item, keys?.value || "value") !== activeValue;
203
+ }
204
+ return item !== activeValue;
205
+ });
206
+ } else {
207
+ const item = isValueObj ? objVal : activeValue;
208
+
209
+ currentValues.push(item as SelectValue);
210
+ }
211
+ return currentValues;
212
+ }
213
+ };
214
+
215
+ // 计算onChange事件回调的selectedOptions参数
216
+ export const getSelectedOptions = (
217
+ value: SelectValue,
218
+ multiple: TdSelectProps["multiple"],
219
+ valueType: TdSelectProps["valueType"],
220
+ keys: SelectKeysType,
221
+ valueToOption: ValueToOption,
222
+ selectedValue?: SelectValue
223
+ ) => {
224
+ const isObjectType = valueType === "object";
225
+ // 当前所有选中的选项
226
+ let currentSelectedOptions = [];
227
+ // 当前选中的选项
228
+ let currentOption: SelectOption;
229
+ // 全选值
230
+ let allSelectedValue: Array<SelectValue>;
231
+ // 所有可选项
232
+ const tmpPropOptions = Object.values(valueToOption);
233
+ if (multiple) {
234
+ currentSelectedOptions = isObjectType
235
+ ? (value as Array<SelectValue>)
236
+ : tmpPropOptions?.filter?.((v) => (value as Array<string | number>).includes?.(v[keys?.value || "value"]));
237
+
238
+ allSelectedValue = isObjectType
239
+ ? currentSelectedOptions
240
+ : currentSelectedOptions?.map((v) => v[keys?.value || "value"]);
241
+
242
+ currentOption = isObjectType
243
+ ? (value as Array<SelectValue>).find((v) => v[keys?.value || "value"] === selectedValue)
244
+ : currentSelectedOptions?.find((option) => option[keys?.value || "value"] === selectedValue);
245
+ } else {
246
+ currentSelectedOptions = isObjectType
247
+ ? [value]
248
+ : tmpPropOptions?.filter?.((v) => value === v[keys?.value || "value"]) || [];
249
+ allSelectedValue = currentSelectedOptions;
250
+ currentOption = isObjectType
251
+ ? value
252
+ : currentSelectedOptions?.find((option) => option[keys?.value || "value"] === selectedValue);
253
+ }
254
+
255
+ return { currentSelectedOptions, currentOption, allSelectedValue };
256
+ };
@@ -0,0 +1,98 @@
1
+ import React, { useRef, useImperativeHandle } from "react";
2
+ import classNames from "classnames";
3
+ import useSingle from "./hook/useSingle";
4
+ import useMultiple from "./hook/useMultiple";
5
+ import Popup, { PopupRef, PopupVisibleChangeContext } from "../popup";
6
+ import useOverlayInnerStyle from "./hook/useOverlayInnerStyle";
7
+ import { TdSelectInputProps } from "./type";
8
+ import { StyledProps } from "../common";
9
+ import { selectInputDefaultProps } from "./defaultProps";
10
+ import useConfig from "../hooks/useConfig";
11
+ import useDefaultProps from "../hooks/useDefaultProps";
12
+ import { InputRef } from "../input";
13
+
14
+ export interface SelectInputProps extends TdSelectInputProps, StyledProps {
15
+ updateScrollTop?: (content: HTMLDivElement) => void;
16
+ options?: unknown[]; // 参数穿透options, 给SelectInput/SelectInput 自定义选中项呈现的内容和多选状态下设置折叠项内容
17
+ }
18
+
19
+ const SelectInput = React.forwardRef<Partial<PopupRef & InputRef>, SelectInputProps>((originalProps, ref) => {
20
+ const props = useDefaultProps<SelectInputProps>(originalProps, selectInputDefaultProps);
21
+ const { classPrefix: prefix } = useConfig();
22
+ const selectInputRef = useRef<PopupRef>(null);
23
+ const selectInputWrapRef = useRef<HTMLElement>(null);
24
+ const { multiple, value, popupVisible, popupProps, borderless, disabled } = props;
25
+ const { commonInputProps, inputRef, singleInputValue, onInnerClear, renderSelectSingle } = useSingle(props);
26
+
27
+ const { tagInputRef, multipleInputValue, renderSelectMultiple } = useMultiple(props);
28
+ const { tOverlayInnerStyle, innerPopupVisible, onInnerPopupVisibleChange } = useOverlayInnerStyle(props, {
29
+ afterHidePopups: onInnerBlur
30
+ });
31
+
32
+ const popupClasses = classNames([
33
+ props.className,
34
+ `${prefix}-select-input`,
35
+ {
36
+ [`${prefix}-select-input--borderless`]: borderless,
37
+ [`${prefix}-select-input--multiple`]: multiple,
38
+ [`${prefix}-select-input--popup-visible`]: popupVisible ?? innerPopupVisible,
39
+ [`${prefix}-select-input--empty`]: value instanceof Array ? !value.length : !value
40
+ }
41
+ ]);
42
+ useImperativeHandle(ref, () => ({
43
+ ...(selectInputRef.current || {}),
44
+ ...(inputRef.current || {}),
45
+ ...(tagInputRef.current || {})
46
+ }));
47
+
48
+ // 浮层显示的受控与非受控
49
+ const visibleProps = { visible: popupVisible ?? innerPopupVisible };
50
+ function onInnerBlur(ctx: PopupVisibleChangeContext) {
51
+ const inputValue = props.multiple ? multipleInputValue : singleInputValue;
52
+ const params: Parameters<TdSelectInputProps["onBlur"]>[1] = {
53
+ e: ctx.e,
54
+ inputValue
55
+ };
56
+ props.onBlur?.(props.value, params);
57
+ }
58
+ const mainContent = (
59
+ <div className={popupClasses} style={props.style}>
60
+ <Popup
61
+ ref={selectInputRef}
62
+ trigger={popupProps?.trigger || "click"}
63
+ placement="bottom-left"
64
+ content={props.panel}
65
+ hideEmptyPopup={true}
66
+ onVisibleChange={onInnerPopupVisibleChange}
67
+ updateScrollTop={props.updateScrollTop}
68
+ {...visibleProps}
69
+ {...popupProps}
70
+ disabled={disabled}
71
+ overlayInnerStyle={tOverlayInnerStyle}
72
+ >
73
+ {multiple
74
+ ? renderSelectMultiple({
75
+ commonInputProps,
76
+ onInnerClear,
77
+ popupVisible: visibleProps.visible,
78
+ allowInput: props.allowInput
79
+ })
80
+ : renderSelectSingle(visibleProps.visible)}
81
+ </Popup>
82
+ </div>
83
+ );
84
+ if (!props.tips) {
85
+ return mainContent;
86
+ }
87
+
88
+ return (
89
+ <div ref={selectInputWrapRef as React.Ref<HTMLDivElement>} className={`t-select-input__wrap`}>
90
+ {mainContent}
91
+ {props.tips && <div className={`t-input__tips t-input__tips--${props.status || "normal"}`}>{props.tips}</div>}
92
+ </div>
93
+ );
94
+ });
95
+
96
+ SelectInput.displayName = "SelectInput";
97
+
98
+ export default SelectInput;
@@ -0,0 +1,15 @@
1
+ import { TdSelectInputProps } from "./type";
2
+
3
+ export const selectInputDefaultProps: TdSelectInputProps = {
4
+ allowInput: false,
5
+ autoWidth: false,
6
+ autofocus: false,
7
+ borderless: false,
8
+ clearable: false,
9
+ loading: false,
10
+ minCollapsedNum: 0,
11
+ multiple: false,
12
+ readonly: false,
13
+ reserveKeyword: false,
14
+ status: "default"
15
+ };
@@ -0,0 +1,100 @@
1
+ import React, { useRef, MouseEvent } from "react";
2
+ import { isObject } from "lodash-es";
3
+ import classNames from "classnames";
4
+ import { TdSelectInputProps, SelectInputChangeContext, SelectInputKeys, SelectInputValue } from "../type";
5
+ import TagInput, { TagInputValue } from "../../tag-input";
6
+ import { SelectInputCommonProperties } from "../interface";
7
+ import useControlled from "../../hooks/useControlled";
8
+ import useConfig from "../../hooks/useConfig";
9
+ import { InputRef } from "../../input";
10
+ import { StyledProps } from "../../common";
11
+
12
+ export interface RenderSelectMultipleParams {
13
+ commonInputProps: SelectInputCommonProperties;
14
+ onInnerClear: (context: { e: MouseEvent<SVGElement> }) => void;
15
+ popupVisible: boolean;
16
+ allowInput: boolean;
17
+ }
18
+
19
+ const DEFAULT_KEYS = {
20
+ label: "label",
21
+ key: "key",
22
+ children: "children"
23
+ };
24
+
25
+ export interface SelectInputProps extends TdSelectInputProps, StyledProps {
26
+ options?: unknown[]; // 参数穿透options, 给SelectInput/SelectInput 自定义选中项呈现的内容和多选状态下设置折叠项内容
27
+ }
28
+
29
+ export default function useMultiple(props: SelectInputProps) {
30
+ const { value } = props;
31
+ const { classPrefix } = useConfig();
32
+ const tagInputRef = useRef<InputRef>(null);
33
+ const [tInputValue, setTInputValue] = useControlled(props, "inputValue", props.onInputChange);
34
+ const iKeys: SelectInputKeys = { ...DEFAULT_KEYS, ...props.keys };
35
+
36
+ const getTags = () => {
37
+ if (!(value instanceof Array)) {
38
+ if (["", null, undefined].includes(String(value))) return [];
39
+ return isObject(value) ? [value[iKeys.label]] : [value];
40
+ }
41
+ return value.map((item: SelectInputValue) => (isObject(item) ? item[iKeys.label] : item));
42
+ };
43
+ const tags = getTags();
44
+ const tPlaceholder = !tags || !tags.length ? props.placeholder : "";
45
+
46
+ const onTagInputChange = (val: TagInputValue, context: SelectInputChangeContext) => {
47
+ // 避免触发浮层的显示或隐藏
48
+ if (context.trigger === "tag-remove") {
49
+ context.e?.stopPropagation();
50
+ }
51
+ props.onTagChange?.(val, context);
52
+ };
53
+
54
+ const renderSelectMultiple = (p: RenderSelectMultipleParams) => (
55
+ <TagInput
56
+ ref={tagInputRef}
57
+ {...p.commonInputProps}
58
+ autoWidth={props.autoWidth}
59
+ readonly={props.readonly}
60
+ minCollapsedNum={props.minCollapsedNum}
61
+ collapsedItems={props.collapsedItems}
62
+ tag={props.tag}
63
+ valueDisplay={props.valueDisplay}
64
+ placeholder={tPlaceholder}
65
+ options={props.options}
66
+ value={tags}
67
+ inputValue={p.popupVisible && p.allowInput ? tInputValue : ""}
68
+ onChange={onTagInputChange}
69
+ onInputChange={(val, context) => {
70
+ // 筛选器统一特性:筛选器按下回车时不清空输入框
71
+ if (context?.trigger === "enter" || context?.trigger === "blur") return;
72
+ setTInputValue(val, { trigger: context.trigger, e: context.e });
73
+ }}
74
+ tagProps={props.tagProps}
75
+ onClear={p.onInnerClear}
76
+ // [Important Info]: SelectInput.blur is not equal to TagInput, example: click popup panel
77
+ onFocus={(val, context) => {
78
+ props.onFocus?.(props.value, { ...context, tagInputValue: val });
79
+ }}
80
+ onBlur={!props.panel ? props.onBlur : null}
81
+ {...props.tagInputProps}
82
+ inputProps={{
83
+ ...props.inputProps,
84
+ readonly: !props.allowInput || props.readonly,
85
+ inputClass: classNames(props.tagInputProps?.className, {
86
+ [`${classPrefix}-input--focused`]: p.popupVisible,
87
+ [`${classPrefix}-is-focused`]: p.popupVisible
88
+ })
89
+ }}
90
+ />
91
+ );
92
+
93
+ return {
94
+ tags,
95
+ tPlaceholder,
96
+ tagInputRef,
97
+ multipleInputValue: tInputValue,
98
+ renderSelectMultiple
99
+ };
100
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useMemo } from "react";
2
+ import { isObject, isFunction } from "lodash-es";
3
+ import useControlled from "../../hooks/useControlled";
4
+ import { TdSelectInputProps } from "../type";
5
+ import { TdPopupProps, PopupVisibleChangeContext } from "../../popup";
6
+
7
+ export type overlayStyleProps = Pick<
8
+ TdSelectInputProps,
9
+ | "popupProps"
10
+ | "autoWidth"
11
+ | "readonly"
12
+ | "onPopupVisibleChange"
13
+ | "disabled"
14
+ | "allowInput"
15
+ | "popupVisible"
16
+ | "defaultPopupVisible"
17
+ >;
18
+
19
+ const MAX_POPUP_WIDTH = 1000;
20
+
21
+ export default function useOverlayInnerStyle(
22
+ props: overlayStyleProps,
23
+ extra?: {
24
+ afterHidePopups?: (ctx: PopupVisibleChangeContext) => void;
25
+ }
26
+ ) {
27
+ const { popupProps, autoWidth, readonly, disabled, onPopupVisibleChange, allowInput } = props;
28
+ const [innerPopupVisible, setInnerPopupVisible] = useControlled(props, "popupVisible", onPopupVisibleChange);
29
+ const matchWidthFunc = (triggerElement: HTMLElement, popupElement: HTMLElement) => {
30
+ if (!triggerElement || !popupElement) {
31
+ return;
32
+ }
33
+
34
+ const prevDisplay = popupElement.style.display;
35
+ popupElement.style.display = "";
36
+ const overlayScrollWidth = popupElement.offsetWidth - popupElement.scrollWidth;
37
+ const width =
38
+ popupElement.offsetWidth - overlayScrollWidth > triggerElement.offsetWidth
39
+ ? popupElement.scrollWidth
40
+ : triggerElement.offsetWidth - overlayScrollWidth;
41
+ if (prevDisplay === "none") {
42
+ popupElement.style.display = "none";
43
+ }
44
+ let otherOverlayInnerStyle: React.CSSProperties = {};
45
+ if (popupProps && typeof popupProps.overlayInnerStyle === "object" && !popupProps.overlayInnerStyle.width) {
46
+ otherOverlayInnerStyle = popupProps.overlayInnerStyle;
47
+ }
48
+ return {
49
+ width: `${Math.min(width, MAX_POPUP_WIDTH)}px`,
50
+ ...otherOverlayInnerStyle
51
+ };
52
+ };
53
+
54
+ const onInnerPopupVisibleChange = (visible: boolean, context: PopupVisibleChangeContext) => {
55
+ if (disabled || readonly) {
56
+ return;
57
+ }
58
+ // 如果点击触发元素(输入框)且为可输入状态,则继续显示下拉框
59
+ const newVisible = context.trigger === "trigger-element-click" && allowInput ? true : visible;
60
+ if (props.popupVisible !== newVisible) {
61
+ setInnerPopupVisible(newVisible, context);
62
+ if (!newVisible) {
63
+ extra?.afterHidePopups?.(context);
64
+ }
65
+ }
66
+ };
67
+ const tOverlayInnerStyle = useMemo(() => {
68
+ let result: TdPopupProps["overlayInnerStyle"] = {};
69
+ const overlayInnerStyle = popupProps?.overlayInnerStyle || {};
70
+ if (isFunction(overlayInnerStyle) || (isObject(overlayInnerStyle) && overlayInnerStyle.width)) {
71
+ result = overlayInnerStyle;
72
+ } else if (!autoWidth) {
73
+ result = matchWidthFunc;
74
+ }
75
+ return result;
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, [autoWidth, popupProps?.overlayInnerStyle]);
78
+
79
+ return {
80
+ tOverlayInnerStyle,
81
+ innerPopupVisible,
82
+ onInnerPopupVisibleChange
83
+ };
84
+ }
@@ -0,0 +1,112 @@
1
+ import React, { useRef, MouseEvent } from "react";
2
+ import { isObject, pick } from "lodash-es";
3
+ import classNames from "classnames";
4
+ import { SelectInputCommonProperties } from "../interface";
5
+ import { TdSelectInputProps, SelectInputValueChangeContext } from "../type";
6
+ import Input, { InputRef, TdInputProps } from "../../input";
7
+ import useControlled from "../../hooks/useControlled";
8
+
9
+ export interface RenderSelectSingleInputParams {
10
+ tPlaceholder: string;
11
+ }
12
+
13
+ // single 和 multiple 共有特性
14
+ const COMMON_PROPERTIES = [
15
+ "status",
16
+ "clearable",
17
+ "disabled",
18
+ "label",
19
+ "placeholder",
20
+ "readonly",
21
+ "suffix",
22
+ "suffixIcon",
23
+ "onPaste",
24
+ "onEnter",
25
+ "onMouseenter",
26
+ "onMouseleave",
27
+ "size",
28
+ "prefixIcon"
29
+ ];
30
+
31
+ const DEFAULT_KEYS: TdSelectInputProps["keys"] = {
32
+ label: "label",
33
+ value: "value"
34
+ };
35
+
36
+ function getInputValue(value: TdSelectInputProps["value"], keys: TdSelectInputProps["keys"]) {
37
+ const iKeys = keys || DEFAULT_KEYS;
38
+ return isObject(value) ? value[iKeys.label] : value;
39
+ }
40
+
41
+ export default function useSingle(props: TdSelectInputProps) {
42
+ const { value, keys } = props;
43
+ const inputRef = useRef<InputRef>(null);
44
+ const [inputValue, setInputValue] = useControlled(props, "inputValue", props.onInputChange);
45
+ const commonInputProps: SelectInputCommonProperties = {
46
+ ...pick(props, COMMON_PROPERTIES),
47
+ suffixIcon: props.suffixIcon
48
+ };
49
+
50
+ const onInnerClear = (context: { e: MouseEvent<SVGSVGElement> }) => {
51
+ context?.e?.stopPropagation();
52
+ props.onClear?.(context);
53
+ setInputValue("", { trigger: "clear" });
54
+ };
55
+
56
+ const onInnerInputChange: TdInputProps["onChange"] = (value, context) => {
57
+ if (props.allowInput) {
58
+ setInputValue(value, { ...context, trigger: "input" } as SelectInputValueChangeContext);
59
+ }
60
+ };
61
+
62
+ const handleEmptyPanelBlur = (value: string, { e }: { e: React.FocusEvent<HTMLInputElement> }) => {
63
+ props.onBlur?.(value, { e, inputValue: value });
64
+ };
65
+
66
+ const renderSelectSingle = (popupVisible: boolean) => {
67
+ const singleValueDisplay = !props.multiple ? props.valueDisplay : null;
68
+ const displayedValue = popupVisible && props.allowInput ? inputValue : getInputValue(value, keys);
69
+ return (
70
+ <Input
71
+ ref={inputRef as unknown as React.Ref<HTMLInputElement>}
72
+ {...commonInputProps}
73
+ autoWidth={props.autoWidth}
74
+ placeholder={props.placeholder}
75
+ value={singleValueDisplay ? " " : displayedValue}
76
+ label={
77
+ (props.label || singleValueDisplay) && (
78
+ <>
79
+ {props.label}
80
+ {singleValueDisplay as React.ReactNode}
81
+ </>
82
+ )
83
+ }
84
+ onChange={onInnerInputChange}
85
+ onClear={onInnerClear}
86
+ // [Important Info]: SelectInput.blur is not equal to Input, example: click popup panel
87
+ onFocus={(val, context) => {
88
+ props.onFocus?.(value, { ...context, inputValue: val });
89
+ // focus might not need to change input value. it will caught some curious errors in tree-select
90
+ // !popupVisible && setInputValue(getInputValue(value, keys), { ...context, trigger: 'input' });
91
+ }}
92
+ onEnter={(val, context) => {
93
+ props.onEnter?.(value, { ...context, inputValue: val });
94
+ }}
95
+ // onBlur need to triggered by input when popup panel is null
96
+ onBlur={!props.panel ? handleEmptyPanelBlur : null}
97
+ {...props.inputProps}
98
+ inputClass={classNames(props.inputProps?.className, {
99
+ [`t-input--focused`]: popupVisible,
100
+ [`t-is-focused`]: popupVisible
101
+ })}
102
+ ></Input>
103
+ );
104
+ };
105
+ return {
106
+ inputRef,
107
+ commonInputProps,
108
+ singleInputValue: inputValue,
109
+ onInnerClear,
110
+ renderSelectSingle
111
+ };
112
+ }
@@ -0,0 +1,6 @@
1
+ import _SelectInput from "./SelectInput";
2
+ import "./style/index.js";
3
+ export type { SelectInputProps } from "./SelectInput";
4
+ export * from "./type";
5
+ export const SelectInput = _SelectInput;
6
+ export default SelectInput;
@@ -0,0 +1,18 @@
1
+ import { TdSelectInputProps } from "./type";
2
+
3
+ export interface SelectInputCommonProperties {
4
+ status?: TdSelectInputProps["status"];
5
+ tips?: TdSelectInputProps["tips"];
6
+ clearable?: TdSelectInputProps["clearable"];
7
+ disabled?: TdSelectInputProps["disabled"];
8
+ label?: TdSelectInputProps["label"];
9
+ placeholder?: TdSelectInputProps["placeholder"];
10
+ readonly?: TdSelectInputProps["readonly"];
11
+ suffix?: TdSelectInputProps["suffix"];
12
+ suffixIcon?: TdSelectInputProps["suffixIcon"];
13
+ size?: TdSelectInputProps["size"];
14
+ onPaste?: TdSelectInputProps["onPaste"];
15
+ onEnter?: TdSelectInputProps["onEnter"];
16
+ onMouseenter?: TdSelectInputProps["onMouseenter"];
17
+ onMouseleave?: TdSelectInputProps["onMouseleave"];
18
+ }
@@ -0,0 +1 @@
1
+ import "./index.css";