x-ui-design 1.0.30 → 1.0.31-gamma.1

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 (219) hide show
  1. package/README.md +1 -22
  2. package/dist/{esm/types/components → components}/DatePicker/DatePicker.d.ts +1 -2
  3. package/dist/{esm/types/components → components}/DatePicker/RangePicker/RangePicker.d.ts +1 -2
  4. package/dist/{esm/types/components → components}/Dropdown/Dropdown.d.ts +1 -2
  5. package/dist/{esm/types/components → components}/Empty/Empty.d.ts +1 -2
  6. package/dist/{esm/types/components → components}/Form/Item/Item.d.ts +1 -2
  7. package/dist/components/Icons/Icons.d.ts +18 -0
  8. package/dist/components/Input/Input.d.ts +11 -0
  9. package/dist/{esm/types/components → components}/Input/Textarea/Textarea.d.ts +1 -2
  10. package/dist/{esm/types/components → components}/Popover/Popover.d.ts +1 -2
  11. package/dist/{esm/types/components → components}/Radio/Button/Button.d.ts +1 -2
  12. package/dist/{esm/types/components → components}/Radio/Group/Group.d.ts +1 -2
  13. package/dist/{esm/types/components → components}/Radio/Radio.d.ts +1 -2
  14. package/dist/{esm/types/components → components}/Result/Result.d.ts +1 -2
  15. package/dist/{esm/types/components → components}/Switch/Switch.d.ts +1 -2
  16. package/dist/{esm/types/components → components}/Upload/Upload.d.ts +1 -2
  17. package/dist/esm/types/index.d.ts +0 -56
  18. package/dist/index.d.ts +1 -150
  19. package/dist/index.esm.js +3433 -3863
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.js +3395 -3825
  22. package/dist/index.js.map +1 -1
  23. package/package.json +22 -20
  24. package/.github/workflows/x-ui-design.yml +0 -14
  25. package/compile.sh +0 -15
  26. package/dist/esm/types/components/Icons/Icons.d.ts +0 -19
  27. package/dist/esm/types/components/Input/Input.d.ts +0 -16
  28. package/eslint.config.mjs +0 -16
  29. package/lib/components/Button/Button.tsx +0 -136
  30. package/lib/components/Button/index.ts +0 -1
  31. package/lib/components/Button/style.css +0 -197
  32. package/lib/components/Checkbox/Checkbox.tsx +0 -131
  33. package/lib/components/Checkbox/index.ts +0 -1
  34. package/lib/components/Checkbox/style.css +0 -95
  35. package/lib/components/ConditionalWrapper/index.tsx +0 -12
  36. package/lib/components/DatePicker/DatePicker.tsx +0 -526
  37. package/lib/components/DatePicker/RangePicker/RangePicker.tsx +0 -500
  38. package/lib/components/DatePicker/RangePicker/index.ts +0 -1
  39. package/lib/components/DatePicker/RangePicker/style.css +0 -434
  40. package/lib/components/DatePicker/TimePicker/TimePicker.tsx +0 -497
  41. package/lib/components/DatePicker/TimePicker/index.ts +0 -1
  42. package/lib/components/DatePicker/TimePicker/style.css +0 -197
  43. package/lib/components/DatePicker/index.ts +0 -1
  44. package/lib/components/DatePicker/style.css +0 -318
  45. package/lib/components/Dropdown/Dropdown.tsx +0 -234
  46. package/lib/components/Dropdown/index.ts +0 -1
  47. package/lib/components/Dropdown/style.css +0 -124
  48. package/lib/components/Empty/Empty.tsx +0 -45
  49. package/lib/components/Empty/index.ts +0 -1
  50. package/lib/components/Empty/style.css +0 -13
  51. package/lib/components/Form/Form.tsx +0 -130
  52. package/lib/components/Form/Item/Item.tsx +0 -294
  53. package/lib/components/Form/Item/index.ts +0 -1
  54. package/lib/components/Form/Item/style.css +0 -61
  55. package/lib/components/Form/index.ts +0 -1
  56. package/lib/components/Icons/Icons.tsx +0 -433
  57. package/lib/components/Icons/index.ts +0 -15
  58. package/lib/components/Input/Input.tsx +0 -228
  59. package/lib/components/Input/Textarea/Textarea.tsx +0 -110
  60. package/lib/components/Input/Textarea/index.ts +0 -1
  61. package/lib/components/Input/Textarea/style.css +0 -104
  62. package/lib/components/Input/index.ts +0 -1
  63. package/lib/components/Input/style.css +0 -137
  64. package/lib/components/Menu/Item/Item.tsx +0 -65
  65. package/lib/components/Menu/Menu.tsx +0 -261
  66. package/lib/components/Menu/SubMenu/SubMenu.tsx +0 -68
  67. package/lib/components/Menu/index.css +0 -145
  68. package/lib/components/Menu/index.ts +0 -1
  69. package/lib/components/Popover/Popover.tsx +0 -135
  70. package/lib/components/Popover/index.ts +0 -1
  71. package/lib/components/Popover/style.css +0 -82
  72. package/lib/components/Radio/Button/Button.tsx +0 -42
  73. package/lib/components/Radio/Button/index.ts +0 -1
  74. package/lib/components/Radio/Button/style.css +0 -43
  75. package/lib/components/Radio/Group/Group.tsx +0 -105
  76. package/lib/components/Radio/Group/index.ts +0 -1
  77. package/lib/components/Radio/Group/style.css +0 -53
  78. package/lib/components/Radio/Radio.tsx +0 -83
  79. package/lib/components/Radio/index.ts +0 -1
  80. package/lib/components/Radio/style.css +0 -73
  81. package/lib/components/Result/Result.tsx +0 -39
  82. package/lib/components/Result/index.ts +0 -1
  83. package/lib/components/Result/style.css +0 -173
  84. package/lib/components/Select/Option/Option.tsx +0 -49
  85. package/lib/components/Select/Option/index.ts +0 -1
  86. package/lib/components/Select/Option/style.css +0 -50
  87. package/lib/components/Select/Select.tsx +0 -935
  88. package/lib/components/Select/Tag/Tag.tsx +0 -43
  89. package/lib/components/Select/Tag/index.ts +0 -1
  90. package/lib/components/Select/Tag/style.css +0 -87
  91. package/lib/components/Select/index.ts +0 -1
  92. package/lib/components/Select/style.css +0 -186
  93. package/lib/components/Skeleton/Avatar/Avatar.tsx +0 -61
  94. package/lib/components/Skeleton/Avatar/index.ts +0 -1
  95. package/lib/components/Skeleton/Avatar/style.css +0 -27
  96. package/lib/components/Skeleton/Button/Button.tsx +0 -44
  97. package/lib/components/Skeleton/Button/index.ts +0 -1
  98. package/lib/components/Skeleton/Button/style.css +0 -50
  99. package/lib/components/Skeleton/Image/Image.tsx +0 -45
  100. package/lib/components/Skeleton/Image/index.ts +0 -1
  101. package/lib/components/Skeleton/Image/style.css +0 -23
  102. package/lib/components/Skeleton/Input/Input.tsx +0 -42
  103. package/lib/components/Skeleton/Input/index.ts +0 -1
  104. package/lib/components/Skeleton/Input/style.css +0 -56
  105. package/lib/components/Skeleton/Skeleton.tsx +0 -97
  106. package/lib/components/Skeleton/index.ts +0 -1
  107. package/lib/components/Skeleton/style.css +0 -84
  108. package/lib/components/Switch/Switch.tsx +0 -68
  109. package/lib/components/Switch/index.css +0 -50
  110. package/lib/components/Switch/index.ts +0 -1
  111. package/lib/components/Upload/Upload.tsx +0 -291
  112. package/lib/components/Upload/index.ts +0 -1
  113. package/lib/components/Upload/style.css +0 -151
  114. package/lib/global.d.ts +0 -1
  115. package/lib/helpers/flatten.ts +0 -26
  116. package/lib/helpers/index.ts +0 -52
  117. package/lib/helpers/mask.ts +0 -52
  118. package/lib/hooks/useForm.ts +0 -548
  119. package/lib/hooks/usePosition.ts +0 -206
  120. package/lib/hooks/useWatch.ts +0 -41
  121. package/lib/hooks/useWatchError.ts +0 -20
  122. package/lib/index.ts +0 -184
  123. package/lib/styles/global.css +0 -57
  124. package/lib/types/button.ts +0 -83
  125. package/lib/types/checkbox.ts +0 -32
  126. package/lib/types/datepicker.ts +0 -165
  127. package/lib/types/dropdown.ts +0 -41
  128. package/lib/types/empty.ts +0 -8
  129. package/lib/types/form.ts +0 -179
  130. package/lib/types/index.ts +0 -38
  131. package/lib/types/input.ts +0 -72
  132. package/lib/types/menu.ts +0 -55
  133. package/lib/types/popover.ts +0 -16
  134. package/lib/types/radio.ts +0 -69
  135. package/lib/types/result.ts +0 -22
  136. package/lib/types/select.ts +0 -126
  137. package/lib/types/skeleton.ts +0 -62
  138. package/lib/types/switch.ts +0 -22
  139. package/lib/types/upload.ts +0 -67
  140. package/lib/utils/index.ts +0 -37
  141. package/lib/utils/lazy.ts +0 -17
  142. package/next.config.ts +0 -7
  143. package/rollup.config.js +0 -71
  144. package/src/app/favicon.ico +0 -0
  145. package/src/app/globals.css +0 -48
  146. package/src/app/layout.d.ts +0 -5
  147. package/src/app/layout.tsx +0 -16
  148. package/src/app/page.d.ts +0 -1
  149. package/src/app/page.tsx +0 -21
  150. package/tsconfig.json +0 -46
  151. /package/dist/{esm/types/components → components}/Button/Button.d.ts +0 -0
  152. /package/dist/{esm/types/components → components}/Button/index.d.ts +0 -0
  153. /package/dist/{esm/types/components → components}/Checkbox/Checkbox.d.ts +0 -0
  154. /package/dist/{esm/types/components → components}/Checkbox/index.d.ts +0 -0
  155. /package/dist/{esm/types/components → components}/ConditionalWrapper/index.d.ts +0 -0
  156. /package/dist/{esm/types/components → components}/DatePicker/RangePicker/index.d.ts +0 -0
  157. /package/dist/{esm/types/components → components}/DatePicker/TimePicker/TimePicker.d.ts +0 -0
  158. /package/dist/{esm/types/components → components}/DatePicker/TimePicker/index.d.ts +0 -0
  159. /package/dist/{esm/types/components → components}/DatePicker/index.d.ts +0 -0
  160. /package/dist/{esm/types/components → components}/Dropdown/index.d.ts +0 -0
  161. /package/dist/{esm/types/components → components}/Empty/index.d.ts +0 -0
  162. /package/dist/{esm/types/components → components}/Form/Form.d.ts +0 -0
  163. /package/dist/{esm/types/components → components}/Form/Item/index.d.ts +0 -0
  164. /package/dist/{esm/types/components → components}/Form/index.d.ts +0 -0
  165. /package/dist/{esm/types/components → components}/Icons/index.d.ts +0 -0
  166. /package/dist/{esm/types/components → components}/Input/Textarea/index.d.ts +0 -0
  167. /package/dist/{esm/types/components → components}/Input/index.d.ts +0 -0
  168. /package/dist/{esm/types/components → components}/Menu/Item/Item.d.ts +0 -0
  169. /package/dist/{esm/types/components → components}/Menu/Menu.d.ts +0 -0
  170. /package/dist/{esm/types/components → components}/Menu/SubMenu/SubMenu.d.ts +0 -0
  171. /package/dist/{esm/types/components → components}/Menu/index.d.ts +0 -0
  172. /package/dist/{esm/types/components → components}/Popover/index.d.ts +0 -0
  173. /package/dist/{esm/types/components → components}/Radio/Button/index.d.ts +0 -0
  174. /package/dist/{esm/types/components → components}/Radio/Group/index.d.ts +0 -0
  175. /package/dist/{esm/types/components → components}/Radio/index.d.ts +0 -0
  176. /package/dist/{esm/types/components → components}/Result/index.d.ts +0 -0
  177. /package/dist/{esm/types/components → components}/Select/Option/Option.d.ts +0 -0
  178. /package/dist/{esm/types/components → components}/Select/Option/index.d.ts +0 -0
  179. /package/dist/{esm/types/components → components}/Select/Select.d.ts +0 -0
  180. /package/dist/{esm/types/components → components}/Select/Tag/Tag.d.ts +0 -0
  181. /package/dist/{esm/types/components → components}/Select/Tag/index.d.ts +0 -0
  182. /package/dist/{esm/types/components → components}/Select/index.d.ts +0 -0
  183. /package/dist/{esm/types/components → components}/Skeleton/Avatar/Avatar.d.ts +0 -0
  184. /package/dist/{esm/types/components → components}/Skeleton/Avatar/index.d.ts +0 -0
  185. /package/dist/{esm/types/components → components}/Skeleton/Button/Button.d.ts +0 -0
  186. /package/dist/{esm/types/components → components}/Skeleton/Button/index.d.ts +0 -0
  187. /package/dist/{esm/types/components → components}/Skeleton/Image/Image.d.ts +0 -0
  188. /package/dist/{esm/types/components → components}/Skeleton/Image/index.d.ts +0 -0
  189. /package/dist/{esm/types/components → components}/Skeleton/Input/Input.d.ts +0 -0
  190. /package/dist/{esm/types/components → components}/Skeleton/Input/index.d.ts +0 -0
  191. /package/dist/{esm/types/components → components}/Skeleton/Skeleton.d.ts +0 -0
  192. /package/dist/{esm/types/components → components}/Skeleton/index.d.ts +0 -0
  193. /package/dist/{esm/types/components → components}/Switch/index.d.ts +0 -0
  194. /package/dist/{esm/types/components → components}/Upload/index.d.ts +0 -0
  195. /package/dist/{esm/types/helpers → helpers}/flatten.d.ts +0 -0
  196. /package/dist/{esm/types/helpers → helpers}/index.d.ts +0 -0
  197. /package/dist/{esm/types/helpers → helpers}/mask.d.ts +0 -0
  198. /package/dist/{esm/types/hooks → hooks}/useForm.d.ts +0 -0
  199. /package/dist/{esm/types/hooks → hooks}/usePosition.d.ts +0 -0
  200. /package/dist/{esm/types/hooks → hooks}/useWatch.d.ts +0 -0
  201. /package/dist/{esm/types/hooks → hooks}/useWatchError.d.ts +0 -0
  202. /package/dist/{esm/types/types → types}/button.d.ts +0 -0
  203. /package/dist/{esm/types/types → types}/checkbox.d.ts +0 -0
  204. /package/dist/{esm/types/types → types}/datepicker.d.ts +0 -0
  205. /package/dist/{esm/types/types → types}/dropdown.d.ts +0 -0
  206. /package/dist/{esm/types/types → types}/empty.d.ts +0 -0
  207. /package/dist/{esm/types/types → types}/form.d.ts +0 -0
  208. /package/dist/{esm/types/types → types}/index.d.ts +0 -0
  209. /package/dist/{esm/types/types → types}/input.d.ts +0 -0
  210. /package/dist/{esm/types/types → types}/menu.d.ts +0 -0
  211. /package/dist/{esm/types/types → types}/popover.d.ts +0 -0
  212. /package/dist/{esm/types/types → types}/radio.d.ts +0 -0
  213. /package/dist/{esm/types/types → types}/result.d.ts +0 -0
  214. /package/dist/{esm/types/types → types}/select.d.ts +0 -0
  215. /package/dist/{esm/types/types → types}/skeleton.d.ts +0 -0
  216. /package/dist/{esm/types/types → types}/switch.d.ts +0 -0
  217. /package/dist/{esm/types/types → types}/upload.d.ts +0 -0
  218. /package/dist/{esm/types/utils → utils}/index.d.ts +0 -0
  219. /package/dist/{esm/types/utils → utils}/lazy.d.ts +0 -0
@@ -1,935 +0,0 @@
1
- 'use client';
2
-
3
- import React, {
4
- Children,
5
- CSSProperties,
6
- Fragment,
7
- isValidElement,
8
- KeyboardEvent,
9
- memo,
10
- ReactElement,
11
- ReactNode,
12
- Suspense,
13
- useCallback,
14
- useEffect,
15
- useImperativeHandle,
16
- useLayoutEffect,
17
- useMemo,
18
- useRef,
19
- useState
20
- } from 'react';
21
- import { createPortal } from 'react-dom';
22
- import ReactDOMServer from 'react-dom/server';
23
- import {
24
- ArrowIcon,
25
- CheckIcon,
26
- ClearIcon,
27
- ErrorIcon,
28
- LoadingIcon,
29
- SearchIcon
30
- } from '../Icons/Icons';
31
- import { clsx } from '../../helpers';
32
- import { MouseEventHandlerSelect, SyntheticBaseEvent } from '../../types';
33
- import { OptionType, SelectProps } from '../../types/select';
34
- import { prefixClsForm, prefixClsSelect, prefixClsSelectV3 } from '../../utils';
35
- import Option from './Option/Option';
36
- import Tag from './Tag/Tag';
37
- import { Empty } from '../Empty';
38
- import { ConditionalWrapper } from '../ConditionalWrapper';
39
- import './style.css';
40
-
41
- const LIST_HEIGHT = 200;
42
- const PADDING_PLACEMENT = 16;
43
- const PADDING_TAG_INPUT = 4;
44
- const FORM_MARGIN_BOTTOM = 20;
45
-
46
- function getTextFromNode(node: ReactNode): string {
47
- if (typeof node === 'string' || typeof node === 'number') {
48
- return node.toString();
49
- }
50
-
51
- if (isValidElement(node)) {
52
- const html = ReactDOMServer.renderToStaticMarkup(node);
53
- return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
54
- }
55
-
56
- return '';
57
- }
58
-
59
- const Select = ({
60
- prefixCls = prefixClsSelect,
61
- prefixClsV3 = prefixClsSelectV3,
62
- id,
63
- searchValue = '',
64
- autoClearSearchValue = true,
65
- filterOption = true,
66
- optionFilterProp,
67
- children,
68
- options = [],
69
- listHeight = LIST_HEIGHT,
70
- menuItemSelectedIcon,
71
- mode = 'default',
72
- value,
73
- defaultValue,
74
- maxCount,
75
- disabled = false,
76
- loading = false,
77
- placeholder = 'Select',
78
- allowClear = false,
79
- filterable = false,
80
- defaultOpen = false,
81
- size = 'large',
82
- error = false,
83
- dropdownClassName = '',
84
- className = '',
85
- suffixIcon,
86
- searchIcon,
87
- style,
88
- showSearch = false,
89
- open = true,
90
- closeFromParent = false,
91
- showArrow = true,
92
- notFoundContent = false,
93
- noStyle,
94
- feedbackIcons,
95
- placement = 'bottomLeft',
96
- removeIcon,
97
- maxTagCount,
98
- iconClickClear,
99
- onSearch,
100
- onSelect,
101
- onDeselect,
102
- onClear,
103
- onChange,
104
- onClose,
105
- tagRender,
106
- getPopupContainer,
107
- dropdownRender,
108
- onDropdownVisibleChange,
109
- iconClick,
110
- ref,
111
- controlled
112
- }: SelectProps
113
- ): ReactElement => {
114
- const asTag = mode === 'tags';
115
- const asMultiple = mode === 'multiple';
116
- const hasMode = asTag || asMultiple;
117
-
118
- const initialValue = useMemo(
119
- () => value ?? defaultValue ?? '',
120
- [value]
121
- );
122
-
123
- const checkModeInitialValue = useMemo(
124
- () =>
125
- (!Array.isArray(initialValue) ? [initialValue] : initialValue).filter(
126
- e => e !== undefined && e !== ''
127
- ),
128
- [initialValue]
129
- );
130
-
131
- const [isHover, setIsHover] = useState(false);
132
- const selectRef = useRef<HTMLDivElement>(null);
133
- const [searchInputWidth, setSearchInputWidth] = useState<number>(0);
134
- const [isOpen, setIsOpen] = useState(defaultOpen);
135
- const [searchFocused, setSearchFocused] = useState(false);
136
- const [isOpenChecker, setIsOpenChecker] = useState(isOpen);
137
- const [searchQuery, setSearchQuery] = useState(searchValue || '');
138
- const [dropdownPosition, setDropdownPosition] = useState<CSSProperties>({});
139
- const [lastTagWidth, setLastTagWidth] = useState(0);
140
-
141
- const tagtriggerRef = useRef<HTMLDivElement>(null);
142
- const searchInputRef = useRef<HTMLDivElement>(null);
143
- const [responsiveTagCount, setResponsiveTagCount] = useState<number | null>(null);
144
-
145
- const [selected, setSelected] = useState(
146
- hasMode ? checkModeInitialValue : initialValue
147
- );
148
-
149
- useImperativeHandle(ref, () => ({
150
- focus: () => selectRef.current?.focus(),
151
- blur: () => (selectRef.current as HTMLInputElement)?.blur(),
152
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
153
- // @ts-expect-error
154
- scrollTo: (...args) =>
155
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
156
- // @ts-expect-error
157
- (selectRef.current as HTMLDivElement)?.scrollTo(...args),
158
- nativeElement: selectRef.current
159
- }), []);
160
-
161
- const handleMouseEnter = useCallback(() =>
162
- !disabled && selected?.length && setIsHover(true), [disabled, selected?.length]);
163
-
164
- const handleMouseLeave = useCallback(() => !disabled && setIsHover(false), [disabled]);
165
-
166
- const handleClearInputValue = useCallback(() => {
167
- if (!autoClearSearchValue) {
168
- return;
169
- }
170
-
171
- setSearchQuery('');
172
-
173
- let inputContainer = selectRef.current?.querySelector(
174
- `[id='${prefixCls}-search-tag-input']`
175
- ) as HTMLDivElement;
176
-
177
- if (!inputContainer) {
178
- let inputContainer = selectRef.current?.querySelector(
179
- `[id='${prefixClsV3}-search-tag-input']`
180
- ) as HTMLDivElement;
181
-
182
- if (!inputContainer) {
183
- inputContainer = selectRef.current?.querySelector(
184
- "[content-editable='plaintext-only']"
185
- ) as HTMLDivElement;
186
- }
187
- }
188
-
189
- if (inputContainer) {
190
- inputContainer.innerText = '';
191
- }
192
- }, [autoClearSearchValue, prefixCls, prefixClsV3]);
193
-
194
- useEffect(() => {
195
- !controlled && setSelected(hasMode ? checkModeInitialValue : initialValue)
196
- }, [checkModeInitialValue, hasMode, initialValue])
197
-
198
- const handleClickOutside = useCallback((event?: MouseEvent): void => {
199
- if (!selectRef.current) return;
200
-
201
- const dropdown = document.querySelector(`.${prefixCls}-dropdown`) || document.querySelector(`.${prefixClsV3}-dropdown`);
202
-
203
- const clickedInside =
204
- selectRef.current.contains(event?.target as Node) ||
205
- (dropdown && dropdown.contains(event?.target as Node));
206
-
207
- if (!clickedInside) {
208
- setSearchFocused(false);
209
- setIsOpen(false);
210
- handleClearInputValue();
211
- onClose?.();
212
- onDropdownVisibleChange?.(false, selected)
213
- }
214
- }, [selectRef.current, prefixCls, prefixClsV3, selected]);
215
-
216
- useEffect(() => {
217
- document.addEventListener('mousedown', handleClickOutside);
218
-
219
- return () => {
220
- document.removeEventListener('mousedown', handleClickOutside);
221
- };
222
- }, [handleClickOutside]);
223
-
224
- const updateDropdownPosition = useCallback((searchQueryUpdated?: boolean) => {
225
- if (!selectRef.current) {
226
- return;
227
- }
228
-
229
- const triggerNode =
230
- (selectRef.current?.querySelector(`.${prefixCls}-trigger`) || selectRef.current?.querySelector(`.${prefixClsV3}-trigger`)) as HTMLElement;
231
-
232
- const selectBox = triggerNode.getBoundingClientRect();
233
- const dropdownHeight = ((getPopupContainer
234
- ? getPopupContainer(triggerNode)
235
- : selectRef.current)?.querySelector(`.${prefixCls}-dropdown`) as HTMLElement)?.clientHeight || listHeight;
236
- const windowHeight = window.innerHeight;
237
- const spaceBelow = windowHeight - selectBox.bottom;
238
- const spaceAbove = selectBox.top;
239
-
240
- let positionStyle: CSSProperties = {
241
- width: `${triggerNode.offsetWidth + PADDING_PLACEMENT}px`,
242
- position: 'absolute',
243
- };
244
-
245
- const shouldShowAbove = spaceBelow < dropdownHeight && spaceAbove > dropdownHeight;
246
- const shouldShowBelow = spaceAbove < dropdownHeight && spaceBelow > dropdownHeight;
247
- const inForm = !triggerNode.closest(`.${prefixClsForm}`) ? FORM_MARGIN_BOTTOM : 0;
248
-
249
- if (isOpen && (shouldShowAbove || shouldShowBelow || searchQueryUpdated || !isOpenChecker)) {
250
- if (getPopupContainer) {
251
- positionStyle = {
252
- ...positionStyle,
253
- top: shouldShowAbove
254
- ? `${selectBox.top + document.documentElement.scrollTop - dropdownHeight + (PADDING_PLACEMENT / 2) + inForm - triggerNode.offsetHeight}px`
255
- : `${selectBox.top + document.documentElement.scrollTop + triggerNode.offsetHeight}px`,
256
- left: `${selectBox.left - (PADDING_PLACEMENT / 2)}px`,
257
- };
258
- } else {
259
- positionStyle = {
260
- ...positionStyle,
261
- top: shouldShowAbove
262
- ? `${(triggerNode.offsetTop - dropdownHeight + PADDING_PLACEMENT) + inForm - triggerNode.offsetHeight}px`
263
- : `${triggerNode.offsetTop + triggerNode.offsetHeight}px`,
264
- left: `${triggerNode.offsetLeft - (PADDING_PLACEMENT / 2)}px`
265
- };
266
- }
267
-
268
- setDropdownPosition(positionStyle);
269
- }
270
- }, [prefixCls, listHeight, getPopupContainer, isOpenChecker, isOpen]);
271
-
272
- useEffect(() => {
273
- setIsOpenChecker(isOpen);
274
-
275
- if (!isOpen) {
276
- setDropdownPosition({});
277
- setSearchFocused(false);
278
- } else {
279
- if (showSearch) {
280
- setSearchFocused(true);
281
- searchInputRef.current?.focus();
282
- }
283
- }
284
- }, [isOpen, showSearch]);
285
-
286
- useEffect(() => {
287
- if (!open && isOpen && closeFromParent) {
288
- handleClickOutside();
289
- }
290
- }, [open, isOpen, closeFromParent])
291
-
292
- useEffect(() => {
293
- if (!isOpen) return;
294
-
295
- const _updateDropdownPosition = () => updateDropdownPosition();
296
-
297
- _updateDropdownPosition();
298
-
299
- const controller = new AbortController();
300
- const scrollableParents = getScrollParents(selectRef.current!);
301
-
302
- scrollableParents.forEach(el => {
303
- el.addEventListener('scroll', _updateDropdownPosition, {
304
- passive: true,
305
- signal: controller.signal
306
- });
307
- });
308
-
309
- window.addEventListener('scroll', _updateDropdownPosition, {
310
- passive: true,
311
- signal: controller.signal
312
- });
313
-
314
- window.addEventListener('resize', _updateDropdownPosition, {
315
- signal: controller.signal
316
- });
317
-
318
- return () => {
319
- controller.abort();
320
- };
321
- }, [isOpen, getPopupContainer, updateDropdownPosition]);
322
-
323
- useEffect(() => {
324
- updateDropdownPosition(true);
325
- }, [searchQuery.length]);
326
-
327
- const getScrollParents = useCallback((element: HTMLElement): HTMLElement[] => {
328
- const parents: HTMLElement[] = [];
329
- let current = element.parentElement;
330
-
331
- while (current) {
332
- if (current.scrollHeight > current.clientHeight) {
333
- parents.push(current);
334
- }
335
- current = current.parentElement;
336
- }
337
-
338
- return parents;
339
- }, []);
340
-
341
- const handleSearch = (e: SyntheticBaseEvent) => {
342
- setSearchQuery(e.target.value as string);
343
- onSearch?.(e.target.value as string);
344
-
345
- if (!isOpen) {
346
- setIsOpen(!isOpen || defaultOpen);
347
- handleClearInputValue();
348
- }
349
- };
350
-
351
- const handleEnterAddNewTag = () => {
352
- if (
353
- asMultiple ||
354
- (maxCount &&
355
- selected.length >= maxCount &&
356
- !selected.includes(searchQuery))
357
- ) {
358
- return;
359
- }
360
-
361
- const newOptionValue = searchQuery.trim();
362
-
363
- if (!newOptionValue || !hasMode || selected.includes(newOptionValue)) {
364
- return;
365
- }
366
-
367
- const updatedSelected = [...selected, newOptionValue];
368
-
369
- onChange?.(updatedSelected);
370
- !controlled && onSelect?.(updatedSelected);
371
-
372
- const input = selectRef.current?.querySelector('input');
373
-
374
- if (input) {
375
- input.value = '';
376
- }
377
-
378
- !controlled && setSelected(updatedSelected);
379
- handleClearInputValue();
380
- };
381
-
382
- const handleSelect = (
383
- e: SyntheticBaseEvent,
384
- optionValue: string,
385
- option?: OptionType
386
- ) => {
387
- if (hasMode) {
388
- if (
389
- maxCount &&
390
- selected.length >= maxCount &&
391
- !selected.includes(optionValue)
392
- ) {
393
- return;
394
- }
395
-
396
- const newSelection = selected.includes(optionValue)
397
- ? (selected as string[]).filter(item => item !== optionValue)
398
- : [...selected, optionValue];
399
-
400
- !controlled && setSelected(newSelection);
401
- onChange?.(newSelection, option);
402
-
403
- if (selected.includes(optionValue)) {
404
- !controlled && onDeselect?.(optionValue, option);
405
- } else {
406
- !controlled && onSelect?.(optionValue, option);
407
- }
408
- } else {
409
- setIsOpen(defaultOpen);
410
- !controlled && setSelected(optionValue);
411
- onChange?.(optionValue, option);
412
- !controlled && onSelect?.(optionValue, option);
413
- }
414
-
415
- handleClearInputValue();
416
- };
417
-
418
- const handleClear = () => {
419
- const value = hasMode ? [] : '';
420
-
421
- !controlled && setSelected(value);
422
- onChange?.('');
423
- !controlled && onSelect?.('');
424
- onClear?.();
425
-
426
- handleClearInputValue();
427
- };
428
-
429
- const handleRemoveTag = (e: SyntheticBaseEvent) => handleSelect(e, e.target.value);
430
-
431
- const handleOnKeyDown = (
432
- e: KeyboardEvent<HTMLInputElement> & {
433
- target: { value: string; innerText: string };
434
- }
435
- ) => {
436
- if (!isOpen || e.which === 13) {
437
- e.stopPropagation();
438
- e.preventDefault();
439
-
440
- return;
441
- }
442
-
443
- const timeout = setTimeout(() => {
444
- setSearchFocused(true);
445
-
446
- e.target.value = (searchInputRef.current?.innerText || e.target.innerText).replace('\n', '');
447
-
448
- setSearchQuery(e.target.value);
449
- onSearch?.(e.target.value);
450
-
451
- if (e.key === 'Enter' && searchQuery.trim() !== '') {
452
- if (!asTag) {
453
- e.stopPropagation();
454
- e.preventDefault();
455
-
456
- clearTimeout(timeout);
457
-
458
- return;
459
- }
460
-
461
- handleEnterAddNewTag();
462
-
463
- e.target.innerText = '';
464
- }
465
-
466
- if (e.key === 'Backspace') {
467
- if (hasMode && !e.target.value.trim().length) {
468
- const updatedSelected = hasMode
469
- ? (selected as string[]).filter(
470
- item => item !== selected[selected.length - 1]
471
- )
472
- : e.target.value.trim();
473
-
474
- if (selected[selected.length - 1]) {
475
- !controlled && onDeselect?.(selected[selected.length - 1]);
476
- }
477
-
478
- onChange?.(updatedSelected);
479
- !controlled && setSelected(updatedSelected);
480
- setSearchFocused(false);
481
- }
482
- }
483
-
484
- clearTimeout(timeout);
485
- });
486
- };
487
-
488
- const ArrowContainer = useMemo(() => {
489
- if (!showArrow) {
490
- return null;
491
- }
492
-
493
- return (
494
- showSearch && isOpen
495
- ? (searchIcon || <SearchIcon />)
496
- : suffixIcon
497
- ? <span style={{ display: 'contents' }} onClick={() => iconClickClear ? handleClear() : iconClick?.()}>{suffixIcon}</span>
498
- : (showArrow && <ArrowIcon isOpen={isOpen} />)
499
- )
500
- }, [showArrow, showSearch, isOpen, suffixIcon, searchIcon]);
501
-
502
- const extractOptions = useCallback((children: ReactNode, options?: OptionType[]) => {
503
- const result: OptionType[] = [];
504
-
505
- const flatten = (nodes: ReactNode): void => {
506
- try {
507
- Children.forEach(nodes, (child) => {
508
- if (!child) return;
509
-
510
- if (isValidElement(child)) {
511
- if (child.type === Fragment || child.type === Suspense) {
512
- flatten((child.props as OptionType).children);
513
- } else {
514
- result.push(child.props as OptionType);
515
- }
516
- }
517
- });
518
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
519
- } catch (e) {
520
- Object.assign(result, nodes)
521
- }
522
- };
523
-
524
- if (children) {
525
- flatten(children);
526
-
527
- return result;
528
- }
529
-
530
- return options || [];
531
- }, [])
532
-
533
- const extractedOptions = useMemo(() => {
534
- return children
535
- ? extractOptions(children)
536
- : Array.isArray(options) ? options : []
537
- }, [children, options]);
538
-
539
- const triggerNode = useMemo(() => {
540
- return selectRef.current?.querySelector(`.${prefixCls}-trigger`) as HTMLElement
541
- }, [prefixCls]);
542
-
543
- const filteredOptions = useMemo(() => {
544
- return extractedOptions.filter((option: OptionType) => {
545
- if (typeof filterOption === 'function') {
546
- return filterOption(searchQuery, option);
547
- }
548
-
549
- if (filterOption === false) {
550
- return true;
551
- }
552
-
553
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
554
- // @ts-expect-error
555
- const optionFilterPropValue = option[optionFilterProp];
556
-
557
- const valueToCheck =
558
- optionFilterProp && typeof optionFilterPropValue === 'string'
559
- ? String(optionFilterPropValue)
560
- : Array.isArray(option.children) && typeof option.children[0] === 'string'
561
- ? option.children[0]
562
- : getTextFromNode(option.children) || String(option.label) || String(option.value);
563
-
564
- return valueToCheck.toLowerCase().includes(searchQuery.toLowerCase());
565
- });
566
- }, [extractedOptions, filterOption, optionFilterProp, searchQuery]);
567
-
568
- const handleTriggerClick = () => {
569
- if (!disabled) {
570
- setIsOpen(!isOpen);
571
- onDropdownVisibleChange?.(!isOpen, selected)
572
- }
573
-
574
- const searchContent = selectRef.current?.getElementsByClassName(
575
- `${prefixCls}-tag-container`
576
- )?.[0] as HTMLDivElement;
577
-
578
- if (searchContent) {
579
- setSearchInputWidth(searchContent.clientWidth - PADDING_TAG_INPUT);
580
- }
581
- };
582
-
583
- const selectedOption = useMemo(() => {
584
- const option =
585
- extractedOptions.find(
586
- (e) => e.value === selected || e.label === selected || e.children === selected
587
- ) || selected;
588
-
589
- const title = typeof option === 'string' ? option : option?.children || option?.label || option?.value || null
590
-
591
- return <div style={{ display: 'contents' }}>{title}</div>;
592
- }, [extractedOptions, selected]) || selected || null;
593
-
594
- const hasMaxTagCount = hasMode && (typeof maxTagCount === 'number' || maxTagCount === 'responsive');
595
- const container = tagtriggerRef.current;
596
- const selectedTags = hasMode ? (selected as string[]) : [];
597
-
598
- const displayTagCount = maxTagCount === 'responsive' ? responsiveTagCount : maxTagCount;
599
- const tagsToDisplay = hasMaxTagCount ? selectedTags.slice(0, displayTagCount || selectedTags.length) : selectedTags;
600
- const overflowCount = hasMaxTagCount ? selectedTags.length - (displayTagCount || selectedTags.length) : 0;
601
- const tags = Array.from(container?.querySelectorAll(`.${prefixCls}-tag:not(.contentEditable):not(.${prefixCls}-tag-overflow)`) || []);
602
-
603
- useLayoutEffect(() => {
604
- if (maxTagCount === 'responsive' && container) {
605
- const containerWidth = container?.clientWidth || 0;
606
- let currentWidth = 0;
607
- let count = 0;
608
-
609
- for (let i = 0; i < tags.length; i++) {
610
- const tag = tags[i] as HTMLElement;
611
-
612
- if (tags.length - 1 === i && overflowCount) {
613
- setLastTagWidth(tag.offsetWidth);
614
- }
615
-
616
- currentWidth += tag.offsetWidth + PADDING_PLACEMENT;
617
-
618
- if (currentWidth < containerWidth) {
619
- count++;
620
- } else {
621
- break;
622
- }
623
- }
624
-
625
- if (overflowCount === 1 && lastTagWidth + PADDING_PLACEMENT) {
626
- setResponsiveTagCount(0);
627
- }
628
-
629
- if (currentWidth >= containerWidth) {
630
- setResponsiveTagCount(count);
631
- }
632
- }
633
- }, [maxTagCount, container, tags, overflowCount]);
634
-
635
- return (
636
- <div
637
- id={id}
638
- ref={selectRef}
639
- style={style}
640
- className={clsx([
641
- {
642
- [size]: size,
643
- noStyle: noStyle,
644
- [prefixCls]: prefixCls,
645
- [prefixClsV3]: prefixClsV3,
646
- [className]: !!className,
647
- [`${prefixCls}-error ${prefixClsV3}-error`]: error,
648
- [`${prefixCls}-multi ${prefixClsV3}-multi`]: hasMode,
649
- [`${prefixCls}-disabled ${prefixClsV3}-disabled`]: disabled
650
- }
651
- ])}
652
- >
653
- <div
654
- tabIndex={0}
655
- role="button"
656
- onClick={handleTriggerClick}
657
- onMouseEnter={handleMouseEnter}
658
- onMouseLeave={handleMouseLeave}
659
- className={`${prefixCls}-trigger ${prefixClsV3}-trigger`}
660
- >
661
- {(showSearch || hasMode) ? (
662
- <div
663
- ref={tagtriggerRef}
664
- style={{
665
- ...style,
666
- ...(isOpen ? { opacity: hasMode || searchQuery.length ? 1 : 0.5, maxWidth: `${searchInputWidth}px` } : {}),
667
- minWidth: `${searchInputWidth}px`
668
- }}
669
- className={clsx([
670
- `${prefixCls}-tag-container ${prefixClsV3}-tag-container`,
671
- {
672
- [`${prefixCls}-tag-container-fixHeight ${prefixClsV3}-tag-container-fixHeight`]: !tagtriggerRef.current
673
- }
674
- ])}
675
- >
676
- {hasMode ? <>
677
- {selectedTags.length ? (
678
- <>
679
- {tagsToDisplay.map((tag, index) =>
680
- tagRender ? (
681
- <div key={`${index}_${tag}`} data-testid={tag}>
682
- {tagRender?.({
683
- label:
684
- (() => {
685
- const option = extractedOptions.find(
686
- e => e.value === tag || e.label === tag || e.children === tag
687
- );
688
-
689
- return option?.children || option?.label || option?.value || null;
690
- })() || tag || null,
691
- value: tag,
692
- onClose: handleRemoveTag,
693
- closable: true
694
- })}
695
- </div>
696
- ) : (
697
- <Tag
698
- closable
699
- value={tag}
700
- label={
701
- (() => {
702
- const option = extractedOptions.find(
703
- e => e.value === tag || e.label === tag || e.children === tag
704
- );
705
-
706
- return option?.children || option?.label || option?.value || null;
707
- })() || tag || null
708
- }
709
- onClose={handleRemoveTag}
710
- key={`${index}_${tag}`}
711
- />
712
- )
713
- )}
714
- {overflowCount > 0 && (
715
- <Tag
716
- label={`+${overflowCount}`}
717
- className={`${prefixCls}-tag-overflow ${prefixClsV3}-tag-overflow`}
718
- />
719
- )}
720
- </>
721
- ) : (
722
- <span style={{ opacity: 0.5 }}>{searchFocused ? '' : placeholder}</span>
723
- )}
724
- </> : null}
725
-
726
- {isOpen ? (
727
- <div className={`${prefixCls}-tag ${prefixClsV3}-tag contentEditable`}>
728
- <div
729
- ref={searchInputRef}
730
- onClick={e => {
731
- if (disabled) {
732
- e.preventDefault();
733
- e.stopPropagation();
734
-
735
- return;
736
- }
737
- }}
738
- onKeyDown={handleOnKeyDown}
739
- style={{
740
- minWidth: showSearch && !searchQuery.length ? 1 : 'auto',
741
- display: 'ruby',
742
- textAlign: 'center',
743
- opacity: searchFocused ? 1 : 0
744
- }}
745
- {...showSearch ? {
746
- contentEditable: true
747
- } : {}}
748
- id={`${prefixCls}-search-tag-input`}
749
- className={`${prefixCls}-tag-input`}
750
- />
751
- {!hasMode && !searchQuery.length ? (selected === ''
752
- ? placeholder
753
- : selectedOption) : null}
754
- </div>
755
- ) : !hasMode ? (
756
- <div
757
- className={`${prefixCls}-input ${prefixClsV3}-input globalEllipsis`}
758
- style={{ opacity: isOpen || selected === '' ? '0.6' : '1' }}
759
- >
760
- {selected === ''
761
- ? placeholder
762
- : selectedOption}
763
- </div>
764
- ) : null}
765
- </div>
766
- ) : !hasMode ? (
767
- <div
768
- className={`${prefixCls}-input ${prefixClsV3}-input globalEllipsis`}
769
- onClick={() => !disabled && setIsOpen(!isOpen || defaultOpen)}
770
- style={{ opacity: isOpen || selected === '' ? '0.6' : '1' }}
771
- >
772
- {selected === ''
773
- ? placeholder
774
- : selectedOption}
775
- </div>
776
- ) : null}
777
-
778
- {isHover && !loading ? (
779
- allowClear && selected ? (
780
- <button
781
- className={`${prefixCls}-clear-btn ${prefixClsV3}-clear-btn`}
782
- onClick={handleClear}
783
- >
784
- {removeIcon || <ClearIcon />}
785
- </button>
786
- ) : (
787
- <span className={`${prefixCls}-arrow ${prefixClsV3}-arrow`}>
788
- {ArrowContainer}
789
- {error && feedbackIcons ? <ErrorIcon /> : null}
790
- </span>
791
- )
792
- ) : (
793
- <>
794
- {!loading && (
795
- <span className={`${prefixCls}-arrow ${prefixClsV3}-arrow`}>
796
- {ArrowContainer}
797
- {error && feedbackIcons ? <ErrorIcon /> : null}
798
- </span>
799
- )}
800
-
801
- {loading && (
802
- <span className={`${prefixCls}-loading ${prefixClsV3}-loading`}>
803
- <LoadingIcon />
804
- </span>
805
- )}
806
- </>
807
- )}
808
- </div>
809
-
810
- <ConditionalWrapper
811
- condition={getPopupContainer !== undefined}
812
- wrapper={(element) => getPopupContainer ? createPortal(element, getPopupContainer(triggerNode)) : <>{element}</>}
813
- >
814
- {!loading && open && isOpen && (
815
- <div
816
- className={clsx([
817
- `${prefixCls}-dropdown ${prefixClsV3}-dropdown`,
818
- {
819
- [placement]: placement,
820
- [dropdownClassName]: dropdownClassName
821
- }
822
- ])}
823
- style={{
824
- ...dropdownPosition,
825
- maxHeight: dropdownRender ? 'unset' : listHeight
826
- }}
827
- >
828
- {filterable && (
829
- <input
830
- type="text"
831
- inputMode="text"
832
- className={`${prefixCls}-search ${prefixClsV3}-search`}
833
- value={searchQuery}
834
- onChange={handleSearch}
835
- placeholder="Search..."
836
- />
837
- )}
838
-
839
- {!loading && (
840
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
841
- // @ts-expect-error
842
- <ConditionalWrapper wrapper={(element) => {
843
- return dropdownRender?.(element || <> </>) || <> </>
844
- }} condition={!!dropdownRender}>
845
- <div
846
- className={`${prefixCls}-options ${prefixClsV3}-options`}
847
- style={{
848
- maxHeight: listHeight,
849
- overflowY: 'auto',
850
- // display: !filteredOptions.length && asTag ? 'none' : 'block',
851
- maxWidth: selectRef.current ? `${selectRef.current.getBoundingClientRect().width}px` : 'inherit',
852
- }}
853
- >
854
- {asTag && !!searchQuery && (
855
- <Option
856
- value={searchQuery}
857
- className={`${prefixCls}-focused ${prefixClsV3}-focused`}
858
- onClick={e => {
859
- handleSelect(e as MouseEventHandlerSelect, searchQuery);
860
- }}
861
- data-value={searchQuery}
862
- data-testid={searchQuery}
863
- >
864
- {searchQuery}
865
- </Option>
866
- )}
867
-
868
- {filteredOptions.length
869
- ? filteredOptions.map(
870
- ({ children, className = '', ...props }, index) => {
871
- const isSelected = hasMode
872
- ? selected.includes(props.value as string)
873
- : props.value === selected;
874
-
875
- return (
876
- <Option
877
- key={`${props.value}_${index}`}
878
- {...props}
879
- selected={isSelected}
880
- className={clsx([
881
- className,
882
- {
883
- [`${prefixCls}-focused ${prefixClsV3}-focused`]: hasMode
884
- ? isSelected
885
- : props.value === selected,
886
- [`${prefixCls}-disabled ${prefixClsV3}-disabled`]:
887
- maxCount && hasMode && !isSelected
888
- ? selected.length >= maxCount
889
- : false
890
- }
891
- ])}
892
- onClick={e => {
893
- if (props.disabled) {
894
- return;
895
- }
896
-
897
- handleSelect(
898
- e as MouseEventHandlerSelect,
899
- props.value as string,
900
- { children, className, ...props, key: `${index}` } as OptionType
901
- );
902
- }}
903
- data-value={props.value}
904
- data-testid={props.value}
905
- >
906
- {children || props.label || props.value}
907
-
908
- {menuItemSelectedIcon && hasMode && isSelected && (
909
- <span className={`${prefixCls}-selected-icon ${prefixClsV3}-selected-icon`}>
910
- {menuItemSelectedIcon === true ? (
911
- <CheckIcon />
912
- ) : (
913
- menuItemSelectedIcon
914
- )}
915
- </span>
916
- )}
917
- </Option>
918
- );
919
- }
920
- ) : !asTag
921
- ? notFoundContent || <Empty />
922
- : null}
923
- </div>
924
- </ConditionalWrapper>
925
- )}
926
- </div>
927
- )}
928
- </ConditionalWrapper>
929
- </div>
930
- );
931
- };
932
-
933
- Select.displayName = 'Select';
934
-
935
- export default Select;