@tendaui/components 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +176 -176
  3. package/alert/Alert.tsx +3 -2
  4. package/button/_example/base.tsx +10 -0
  5. package/button/_example/icon.tsx +20 -0
  6. package/color-picker/ColorPickPanel.tsx +9 -0
  7. package/color-picker/ColorPicker.tsx +67 -0
  8. package/color-picker/components/panel/alpha.tsx +32 -0
  9. package/color-picker/components/panel/format/index.tsx +47 -0
  10. package/color-picker/components/panel/format/inputs.tsx +119 -0
  11. package/color-picker/components/panel/header.tsx +37 -0
  12. package/color-picker/components/panel/hue.tsx +20 -0
  13. package/color-picker/components/panel/index.tsx +191 -0
  14. package/color-picker/components/panel/saturation.tsx +81 -0
  15. package/color-picker/components/panel/slider.tsx +76 -0
  16. package/color-picker/components/panel/swatches.tsx +84 -0
  17. package/color-picker/components/trigger.tsx +49 -0
  18. package/color-picker/defaultProps.ts +7 -0
  19. package/color-picker/helpers.ts +53 -0
  20. package/color-picker/hooks/useClassNames.ts +9 -0
  21. package/color-picker/hooks/useStyles.ts +39 -0
  22. package/color-picker/index.ts +12 -0
  23. package/color-picker/style/css.js +1 -0
  24. package/color-picker/style/index.js +1 -0
  25. package/color-picker/type.ts +143 -0
  26. package/color-picker/utils/color-picker/cmyk.ts +89 -0
  27. package/color-picker/utils/color-picker/color.ts +467 -0
  28. package/color-picker/utils/color-picker/constants.ts +187 -0
  29. package/color-picker/utils/color-picker/draggable.ts +100 -0
  30. package/color-picker/utils/color-picker/format.ts +95 -0
  31. package/color-picker/utils/color-picker/gradient.ts +243 -0
  32. package/color-picker/utils/color-picker/index.ts +7 -0
  33. package/color-picker/utils/color-picker/types.ts +33 -0
  34. package/common/observe.ts +33 -0
  35. package/common.ts +20 -0
  36. package/config-provider/ConfigContext.tsx +4 -1
  37. package/config-provider/index.ts +1 -1
  38. package/dialog/DialogCard.tsx +4 -6
  39. package/dialog/hooks/useDialogPosition.ts +1 -2
  40. package/dialog/plugin.tsx +3 -2
  41. package/drawer/Drawer.tsx +264 -0
  42. package/drawer/defaultProps.ts +19 -0
  43. package/drawer/hooks/useDrag.ts +98 -0
  44. package/drawer/hooks/useLockStyle.ts +36 -0
  45. package/drawer/index.ts +5 -0
  46. package/drawer/style/css.js +1 -0
  47. package/drawer/style/index.js +1 -0
  48. package/drawer/type.ts +193 -0
  49. package/drawer/utils/index.ts +76 -0
  50. package/fireworks/Fireworks.tsx +138 -0
  51. package/fireworks/index.ts +10 -0
  52. package/fireworks/style/css.js +0 -0
  53. package/fireworks/style/index.js +0 -0
  54. package/fireworks/type.ts +72 -0
  55. package/form/FormItem.tsx +5 -5
  56. package/form/easing.ts +10 -0
  57. package/form/scroll.ts +124 -0
  58. package/form/type.ts +519 -519
  59. package/global-config/default-config.ts +95 -0
  60. package/global-config/locale/ar_KW.ts +270 -0
  61. package/global-config/locale/en_US.ts +280 -0
  62. package/global-config/locale/it_IT.ts +287 -0
  63. package/global-config/locale/ja_JP.ts +279 -0
  64. package/global-config/locale/ko_KR.ts +279 -0
  65. package/global-config/locale/ru_RU.ts +288 -0
  66. package/global-config/locale/zh_CN.ts +279 -0
  67. package/global-config/locale/zh_TW.ts +279 -0
  68. package/global-config/mobile/default-config.ts +6 -0
  69. package/global-config/mobile/locale/ar_KW.ts +113 -0
  70. package/global-config/mobile/locale/en_US.ts +114 -0
  71. package/global-config/mobile/locale/it_IT.ts +114 -0
  72. package/global-config/mobile/locale/ja_JP.ts +101 -0
  73. package/global-config/mobile/locale/ko_KR.ts +101 -0
  74. package/global-config/mobile/locale/ru_RU.ts +113 -0
  75. package/global-config/mobile/locale/zh_CN.ts +101 -0
  76. package/global-config/mobile/locale/zh_TW.ts +101 -0
  77. package/global-config/t.ts +111 -0
  78. package/hooks/useControlled.ts +3 -3
  79. package/hooks/useDeepEffect.ts +32 -0
  80. package/hooks/useGlobalIcon.ts +10 -3
  81. package/hooks/useLastest.ts +2 -6
  82. package/hooks/useResizeObserve.ts +36 -0
  83. package/index.ts +10 -7
  84. package/input/Input.tsx +4 -1
  85. package/input/defaultProps.ts +0 -2
  86. package/input/type.ts +1 -6
  87. package/input-number/InputNumber.tsx +124 -0
  88. package/input-number/defaultProps.ts +17 -0
  89. package/input-number/index.ts +9 -0
  90. package/input-number/style/css.js +1 -0
  91. package/input-number/style/index.js +1 -0
  92. package/input-number/type.ts +147 -0
  93. package/input-number/useInputNumber.tsx +270 -0
  94. package/ip-input/IPInput.tsx +516 -0
  95. package/ip-input/defaultProps.ts +11 -0
  96. package/ip-input/index.ts +3 -0
  97. package/ip-input/style/css.js +1 -0
  98. package/ip-input/style/index.js +1 -0
  99. package/ip-input/type.ts +115 -0
  100. package/ip-input/utils.ts +112 -0
  101. package/layout/Aside.tsx +38 -0
  102. package/layout/Layout.tsx +104 -0
  103. package/layout/defaultProps.ts +9 -0
  104. package/layout/index.ts +9 -0
  105. package/layout/style/css.js +1 -0
  106. package/layout/style/index.js +1 -0
  107. package/layout/type.ts +43 -0
  108. package/list/List.tsx +144 -0
  109. package/list/ListItem.tsx +36 -0
  110. package/list/ListItemMeta.tsx +40 -0
  111. package/list/defaultProps.ts +11 -0
  112. package/list/hooks/useListVirtualScroll.ts +82 -0
  113. package/list/index.ts +11 -0
  114. package/list/style/css.js +1 -0
  115. package/list/style/index.js +1 -0
  116. package/list/type.ts +93 -0
  117. package/locale/LocalReceiver.ts +55 -0
  118. package/locale/ar_KW.ts +7 -0
  119. package/locale/en_US.ts +7 -0
  120. package/locale/it_IT.ts +6 -0
  121. package/locale/ja_JP.ts +6 -0
  122. package/locale/ko_KR.ts +6 -0
  123. package/locale/ru_RU.ts +6 -0
  124. package/locale/zh_CN.ts +5 -0
  125. package/locale/zh_TW.ts +7 -0
  126. package/notification/NotifyContainer.tsx +2 -2
  127. package/notification/NotifyContext.tsx +1 -0
  128. package/package.json +6 -3
  129. package/popup/Popup.tsx +34 -10
  130. package/radio/Radio.tsx +24 -0
  131. package/radio/RadioGroup.tsx +159 -0
  132. package/radio/defaultProps.ts +18 -0
  133. package/radio/index.ts +12 -0
  134. package/radio/style/css.js +0 -0
  135. package/radio/style/index.js +1 -0
  136. package/radio/type.ts +115 -0
  137. package/radio/useKeyboard.ts +36 -0
  138. package/select/hooks/useOptions.ts +10 -7
  139. package/select/hooks/usePanelVirtualScroll.ts +1 -1
  140. package/select/type.ts +382 -382
  141. package/select-input/type.ts +280 -280
  142. package/slider/Slider.tsx +270 -0
  143. package/slider/SliderHandleButton.tsx +50 -0
  144. package/slider/defaultProps.ts +15 -0
  145. package/slider/index.ts +9 -0
  146. package/slider/style/css.js +1 -0
  147. package/slider/style/index.js +1 -0
  148. package/slider/type.ts +77 -0
  149. package/style/all.js +26 -0
  150. package/styles/_global.scss +39 -39
  151. package/styles/_vars.scss +358 -386
  152. package/styles/components/alert/_index.scss +175 -175
  153. package/styles/components/alert/_vars.scss +39 -39
  154. package/styles/components/badge/_index.scss +70 -70
  155. package/styles/components/badge/_vars.scss +25 -25
  156. package/styles/components/button/_index.scss +499 -511
  157. package/styles/components/button/_mixins.scss +39 -39
  158. package/styles/components/button/_vars.scss +120 -122
  159. package/styles/components/checkbox/_index.scss +158 -158
  160. package/styles/components/checkbox/_var.scss +60 -60
  161. package/styles/components/color-picker/_index.scss +586 -0
  162. package/styles/components/color-picker/_mixins.scss +0 -0
  163. package/styles/components/color-picker/_vars.scss +84 -0
  164. package/styles/components/dialog/_animate.scss +135 -135
  165. package/styles/components/dialog/_index.scss +311 -311
  166. package/styles/components/dialog/_vars.scss +59 -59
  167. package/styles/components/drawer/_index.scss +205 -0
  168. package/styles/components/drawer/_mixins.scss +1 -0
  169. package/styles/components/drawer/_var.scss +53 -0
  170. package/styles/components/fireworks/_index.scss +86 -0
  171. package/styles/components/fireworks/_vars.scss +4 -0
  172. package/styles/components/form/_index.scss +174 -174
  173. package/styles/components/form/_mixins.scss +76 -76
  174. package/styles/components/form/_vars.scss +100 -100
  175. package/styles/components/input/_index.scss +349 -349
  176. package/styles/components/input/_mixins.scss +116 -116
  177. package/styles/components/input/_vars.scss +134 -134
  178. package/styles/components/input-number/_index.scss +353 -0
  179. package/styles/components/input-number/_mixins.scss +0 -0
  180. package/styles/components/input-number/_vars.scss +65 -0
  181. package/styles/components/ip-input/_index.scss +280 -0
  182. package/styles/components/layout/_index.scss +47 -0
  183. package/styles/components/layout/_mixin.scss +0 -0
  184. package/styles/components/layout/_vars.scss +18 -0
  185. package/styles/components/layout/doc.scss +74 -0
  186. package/styles/components/list/_index.scss +172 -0
  187. package/styles/components/list/_mixins.scss +0 -0
  188. package/styles/components/list/_vars.scss +41 -0
  189. package/styles/components/loading/_index.scss +112 -112
  190. package/styles/components/loading/_vars.scss +39 -39
  191. package/styles/components/notification/_index.scss +160 -160
  192. package/styles/components/notification/_mixins.scss +12 -12
  193. package/styles/components/notification/_vars.scss +59 -59
  194. package/styles/components/popup/_index.scss +82 -82
  195. package/styles/components/popup/_mixin.scss +149 -149
  196. package/styles/components/popup/_var.scss +31 -31
  197. package/styles/components/radio/_index.scss +376 -0
  198. package/styles/components/radio/_mixins.scss +0 -0
  199. package/styles/components/radio/_var.scss +92 -0
  200. package/styles/components/select/_index.scss +290 -290
  201. package/styles/components/select/_var.scss +65 -65
  202. package/styles/components/select-input/_index.scss +5 -5
  203. package/styles/components/select-input/_var.scss +3 -3
  204. package/styles/components/slider/_index.scss +241 -0
  205. package/styles/components/slider/_mixins.scss +0 -0
  206. package/styles/components/slider/_vars.scss +50 -0
  207. package/styles/components/switch/_index.scss +279 -279
  208. package/styles/components/switch/_vars.scss +61 -61
  209. package/styles/components/table/_index.scss +193 -0
  210. package/styles/components/table/_var.scss +52 -0
  211. package/styles/components/tabs/_index.scss +165 -0
  212. package/styles/components/tabs/_mixins.scss +11 -0
  213. package/styles/components/tabs/_vars.scss +71 -0
  214. package/styles/components/tag/_index.scss +316 -316
  215. package/styles/components/tag/_var.scss +85 -85
  216. package/styles/components/tag-input/_index.scss +163 -163
  217. package/styles/components/tag-input/_vars.scss +16 -16
  218. package/styles/globals.css +250 -250
  219. package/styles/mixins/_focus.scss +7 -7
  220. package/styles/mixins/_layout.scss +32 -32
  221. package/styles/mixins/_reset.scss +10 -10
  222. package/styles/mixins/_scrollbar.scss +31 -31
  223. package/styles/mixins/_text.scss +48 -48
  224. package/styles/rillple.css +16 -16
  225. package/styles/scrollbar.css +41 -41
  226. package/styles/themes/_dark.scss +191 -191
  227. package/styles/themes/_font.scss +69 -79
  228. package/styles/themes/_index.scss +5 -5
  229. package/styles/themes/_light.scss +190 -190
  230. package/styles/themes/_radius.scss +9 -9
  231. package/styles/themes/_size.scss +68 -68
  232. package/styles/themes.css +66 -66
  233. package/styles/utilities/_animation.scss +57 -57
  234. package/styles/utilities/_tips.scss +9 -9
  235. package/tab/TabBar.tsx +85 -0
  236. package/tab/TabNav.tsx +103 -0
  237. package/tab/TabNavItem.tsx +80 -0
  238. package/tab/TabPanel.tsx +42 -0
  239. package/tab/Tabs.tsx +71 -0
  240. package/tab/defaultProps.ts +19 -0
  241. package/tab/index.ts +7 -0
  242. package/tab/style/index.js +1 -0
  243. package/tab/type.ts +125 -0
  244. package/tab/useTabClass.ts +20 -0
  245. package/table/Cell.tsx +109 -0
  246. package/table/TBody.tsx +77 -0
  247. package/table/THead.tsx +63 -0
  248. package/table/TR.tsx +78 -0
  249. package/table/Table.tsx +73 -0
  250. package/table/defaultProps.ts +14 -0
  251. package/table/hooks/index.ts +4 -0
  252. package/table/hooks/useTableClassName.ts +63 -0
  253. package/table/hooks/useTableStyle.ts +93 -0
  254. package/table/index.ts +7 -0
  255. package/table/style/css.js +1 -0
  256. package/table/style/index.js +1 -0
  257. package/table/type.ts +192 -0
  258. package/tag/Tag.tsx +1 -1
  259. package/tag-input/hooks/useTagList.tsx +1 -1
  260. package/utils/dom.ts +4 -0
  261. package/utils/forwardRefWithStatics.ts +1 -4
  262. package/utils/input-number/large-number.ts +423 -0
  263. package/utils/input-number/number.ts +257 -0
  264. package/utils/isFragment.ts +6 -6
  265. package/utils/log/index.ts +3 -0
  266. package/utils/log/log.ts +30 -0
  267. package/utils/log/types.ts +12 -0
  268. package/utils/number.ts +21 -0
  269. package/utils/scroll.ts +26 -0
  270. package/utils/style.ts +2 -4
@@ -0,0 +1,264 @@
1
+ import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import { CSSTransition } from "react-transition-group";
3
+ import { IconClose as TdCloseIcon } from "@tendaui/icons";
4
+ import classnames from "classnames";
5
+ import { isFunction, isObject, isString, isUndefined } from "lodash-es";
6
+ import parseTNode from "../utils/parentTNode";
7
+ import Button, { type ButtonProps } from "../button";
8
+ import Portal from "../common/Portal";
9
+ import useAttach from "../hooks/useAttach";
10
+ import useConfig from "../hooks/useConfig";
11
+ import useDeepEffect from "../hooks/useDeepEffect";
12
+ import useDefaultProps from "../hooks/useDefaultProps";
13
+ import useGlobalIcon from "../hooks/useGlobalIcon";
14
+ import useSetState from "../hooks/useSetState";
15
+ import { drawerDefaultProps } from "./defaultProps";
16
+ import useDrag from "./hooks/useDrag";
17
+ import { useLockStyle } from "./hooks/useLockStyle";
18
+ import { useLocaleReceiver } from "../locale/LocalReceiver";
19
+
20
+ import type { StyledProps } from "../common";
21
+ import type { DrawerEventSource, DrawerInstance, TdDrawerProps } from "./type";
22
+
23
+ export const CloseTriggerType: { [key: string]: DrawerEventSource } = {
24
+ CLICK_OVERLAY: "overlay",
25
+ CLICK_CLOSE_BTN: "close-btn",
26
+ CLICK_CANCEL_BTN: "cancel",
27
+ KEYDOWN_ESC: "esc"
28
+ };
29
+
30
+ export interface DrawerProps extends TdDrawerProps, StyledProps {
31
+ isPlugin?: boolean; // 是否以插件形式调用
32
+ }
33
+
34
+ const Drawer = forwardRef<DrawerInstance, DrawerProps>((originalProps, ref) => {
35
+ // 国际化文本初始化
36
+ const [local, t] = useLocaleReceiver("drawer");
37
+ const { CloseIcon } = useGlobalIcon({ CloseIcon: TdCloseIcon });
38
+ const confirmText = t(local.confirm);
39
+ const cancelText = t(local.cancel);
40
+
41
+ const props = useDefaultProps<DrawerProps>(originalProps, drawerDefaultProps);
42
+ const { body, children, header, footer, ...restProps } = props;
43
+ const [state, setState] = useSetState<DrawerProps>({ isPlugin: false, ...restProps });
44
+ const {
45
+ className,
46
+ style,
47
+ visible,
48
+ attach,
49
+ showOverlay,
50
+ size: propsSize,
51
+ placement,
52
+ onBeforeOpen,
53
+ onBeforeClose,
54
+ onCancel,
55
+ onConfirm,
56
+ onClose,
57
+ onCloseBtnClick,
58
+ onOverlayClick,
59
+ onEscKeydown,
60
+ onSizeDragEnd,
61
+ showInAttachedElement,
62
+ closeOnOverlayClick,
63
+ closeOnEscKeydown,
64
+ closeBtn,
65
+ cancelBtn = cancelText,
66
+ confirmBtn = confirmText,
67
+ zIndex,
68
+ destroyOnClose,
69
+ sizeDraggable,
70
+ forceRender,
71
+ isPlugin,
72
+ lazy
73
+ } = state;
74
+
75
+ const size = propsSize;
76
+ const { classPrefix } = useConfig();
77
+ const drawerAttach = useAttach("drawer", attach);
78
+ const maskRef = useRef<HTMLDivElement>(null);
79
+ const containerRef = useRef<HTMLDivElement>(null);
80
+ const drawerWrapperRef = useRef<HTMLElement>(null); // 即最终的 attach dom,默认为 document.body
81
+ const prefixCls = `${classPrefix}-drawer`;
82
+
83
+ const closeIcon = isValidElement(closeBtn) ? closeBtn : <CloseIcon />;
84
+ const { dragSizeValue, enableDrag, draggableLineStyles, draggingStyles } = useDrag(
85
+ placement,
86
+ sizeDraggable,
87
+ onSizeDragEnd
88
+ );
89
+ const [animationStart, setAnimationStart] = useState(visible);
90
+
91
+ const sizeValue = useMemo(() => {
92
+ const sizeMap = { small: "300px", medium: "500px", large: "760px" };
93
+ return dragSizeValue || sizeMap[size] || size;
94
+ }, [dragSizeValue, size]);
95
+
96
+ useLockStyle({ ...state, sizeValue, drawerWrapper: drawerWrapperRef.current });
97
+ useImperativeHandle(ref, () => ({
98
+ show() {
99
+ setState({ visible: true });
100
+ },
101
+ hide() {
102
+ setState({ visible: false });
103
+ },
104
+ destroy() {
105
+ setState({ visible: false, destroyOnClose: true });
106
+ },
107
+ update(options) {
108
+ setState((prevState) => ({ ...prevState, ...options }));
109
+ }
110
+ }));
111
+
112
+ useEffect(() => {
113
+ if (visible) {
114
+ // 聚焦到 Drawer 最外层元素即 containerRef.current,KeyDown 事件才有效。
115
+ containerRef.current?.focus?.();
116
+ }
117
+ }, [visible]);
118
+
119
+ useDeepEffect(() => {
120
+ // 非插件式调用 更新props
121
+ if (isPlugin) return;
122
+ setState((prevState) => ({ ...prevState, ...props }));
123
+ }, [props, setState]);
124
+
125
+ function onMaskClick(e: React.MouseEvent<HTMLDivElement>) {
126
+ onOverlayClick?.({ e });
127
+ closeOnOverlayClick && onClose?.({ e, trigger: CloseTriggerType.CLICK_OVERLAY });
128
+ }
129
+ function onClickCloseBtn(e: React.MouseEvent<HTMLDivElement>) {
130
+ onCloseBtnClick?.({ e });
131
+ onClose?.({ e, trigger: CloseTriggerType.CLICK_CLOSE_BTN });
132
+ }
133
+ function onKeyDownEsc(e: React.KeyboardEvent<HTMLDivElement>) {
134
+ if (e.key !== "Escape") return;
135
+
136
+ onEscKeydown?.({ e });
137
+ closeOnEscKeydown && onClose?.({ e, trigger: CloseTriggerType.KEYDOWN_ESC });
138
+ }
139
+ function onCancelClick(e: React.MouseEvent<HTMLButtonElement>) {
140
+ onCancel?.({ e });
141
+ onClose?.({ e, trigger: CloseTriggerType.CLICK_CANCEL_BTN });
142
+ }
143
+ function onConfirmClick(e: React.MouseEvent<HTMLButtonElement>) {
144
+ onConfirm?.({ e });
145
+ }
146
+
147
+ const contentWrapperStyle = useMemo(
148
+ () => ({
149
+ transform: visible && animationStart ? "translateX(0)" : undefined,
150
+ width: ["left", "right"].includes(placement) ? sizeValue : "",
151
+ height: ["top", "bottom"].includes(placement) ? sizeValue : ""
152
+ }),
153
+ [visible, placement, sizeValue, animationStart]
154
+ );
155
+
156
+ const renderDrawerButton = (btn: DrawerProps["cancelBtn"], defaultProps: ButtonProps) => {
157
+ let result = null;
158
+
159
+ if (isString(btn)) {
160
+ result = <Button {...defaultProps}>{btn}</Button>;
161
+ } else if (isValidElement(btn)) {
162
+ result = btn;
163
+ } else if (isObject(btn)) {
164
+ result = <Button {...defaultProps} {...(btn as {})} />;
165
+ } else if (isFunction(btn)) {
166
+ result = btn();
167
+ }
168
+
169
+ return result;
170
+ };
171
+
172
+ const renderFooter = () => {
173
+ const defaultFooter = () => {
174
+ const renderCancelBtn = renderDrawerButton(cancelBtn, {
175
+ theme: "default",
176
+ onClick: (e: React.MouseEvent<HTMLButtonElement>) => onCancelClick?.(e),
177
+ className: `${prefixCls}__cancel`
178
+ });
179
+ const renderConfirmBtn = renderDrawerButton(confirmBtn, {
180
+ theme: "primary",
181
+ onClick: (e: React.MouseEvent<HTMLButtonElement>) => onConfirmClick?.(e),
182
+ className: `${prefixCls}__confirm`
183
+ });
184
+
185
+ const footerStyle = {
186
+ display: "flex",
187
+ justifyContent: placement === "right" ? "flex-start" : "flex-end"
188
+ };
189
+
190
+ return (
191
+ <div style={footerStyle}>
192
+ {placement === "right" ? (
193
+ <>
194
+ {renderConfirmBtn} {renderCancelBtn}
195
+ </>
196
+ ) : (
197
+ <>
198
+ {renderCancelBtn} {renderConfirmBtn}
199
+ </>
200
+ )}
201
+ </div>
202
+ );
203
+ };
204
+
205
+ return <div className={`${prefixCls}__footer`}>{parseTNode(footer, null, defaultFooter())}</div>;
206
+ };
207
+
208
+ const renderOverlay = showOverlay && (
209
+ <CSSTransition in={visible} timeout={200} classNames={`${prefixCls}-fade`} nodeRef={maskRef}>
210
+ <div ref={maskRef} className={`${prefixCls}__mask`} onClick={onMaskClick} />
211
+ </CSSTransition>
212
+ );
213
+ const renderCloseBtn = closeBtn && (
214
+ <div onClick={onClickCloseBtn} className={`${prefixCls}__close-btn`}>
215
+ {closeIcon}
216
+ </div>
217
+ );
218
+ const renderHeader = header && <div className={`${prefixCls}__header`}>{header}</div>;
219
+ const renderBody = <div className={`${prefixCls}__body`}>{body || children}</div>;
220
+
221
+ return (
222
+ <CSSTransition
223
+ in={visible}
224
+ nodeRef={drawerWrapperRef}
225
+ mountOnEnter={isUndefined(forceRender) ? lazy : !forceRender}
226
+ unmountOnExit={destroyOnClose}
227
+ timeout={{ appear: 10, enter: 10, exit: 300 }}
228
+ onEnter={() => onBeforeOpen?.()}
229
+ onEntered={() => setAnimationStart(true)}
230
+ onExit={() => onBeforeClose?.()}
231
+ onExited={() => setAnimationStart(false)}
232
+ >
233
+ <Portal attach={drawerAttach} ref={drawerWrapperRef}>
234
+ <div
235
+ ref={containerRef}
236
+ className={classnames(prefixCls, className, `${prefixCls}--${placement}`, {
237
+ [`${prefixCls}--open`]: visible,
238
+ [`${prefixCls}--attach`]: showInAttachedElement,
239
+ [`${prefixCls}--without-mask`]: !showOverlay
240
+ })}
241
+ style={{ zIndex, ...style }}
242
+ tabIndex={-1} // https://stackoverflow.com/questions/43503964/onkeydown-event-not-working-on-divs-in-react
243
+ onKeyDown={onKeyDownEsc}
244
+ >
245
+ {renderOverlay}
246
+ <div
247
+ className={classnames(`${prefixCls}__content-wrapper`, `${prefixCls}__content-wrapper--${placement}`)}
248
+ style={{ ...contentWrapperStyle, ...draggingStyles }}
249
+ >
250
+ {renderCloseBtn}
251
+ {renderHeader}
252
+ {renderBody}
253
+ {!!footer && renderFooter()}
254
+ {sizeDraggable && <div style={draggableLineStyles} onMouseDown={enableDrag}></div>}
255
+ </div>
256
+ </div>
257
+ </Portal>
258
+ </CSSTransition>
259
+ );
260
+ });
261
+
262
+ Drawer.displayName = "Drawer";
263
+
264
+ export default Drawer;
@@ -0,0 +1,19 @@
1
+ import { TdDrawerProps } from "./type";
2
+
3
+ export const drawerDefaultProps: TdDrawerProps = {
4
+ closeOnEscKeydown: undefined,
5
+ closeOnOverlayClick: undefined,
6
+ closeBtn: true,
7
+ destroyOnClose: false,
8
+ footer: true,
9
+ header: true,
10
+ lazy: true,
11
+ mode: "overlay",
12
+ placement: "right",
13
+ preventScrollThrough: true,
14
+ showInAttachedElement: false,
15
+ showOverlay: true,
16
+ size: undefined,
17
+ sizeDraggable: false,
18
+ visible: false
19
+ };
@@ -0,0 +1,98 @@
1
+ import { useCallback, useMemo, useRef, useState } from "react";
2
+ import { getSizeDraggable, calcMoveSize } from "../utils";
3
+
4
+ import type { TdDrawerProps } from "../type";
5
+ import type { Styles } from "../../common";
6
+
7
+ const useDrag = (
8
+ placement: TdDrawerProps["placement"],
9
+ sizeDraggable: TdDrawerProps["sizeDraggable"],
10
+ onSizeDragEnd: TdDrawerProps["onSizeDragEnd"]
11
+ ) => {
12
+ const [dragSizeValue, changeDragSizeValue] = useState<string>(null);
13
+ // 使用 ref 来存储当前拖拽的宽度值
14
+ const dragSizeRef = useRef<number>(0);
15
+ const [isSizeDragging, toggleSizeDragging] = useState(false);
16
+
17
+ const handleMousemove = useCallback(
18
+ (e: MouseEvent) => {
19
+ // 如果 sizeDraggable 是 boolean 值的 false,则不进行后续的计算
20
+ if (sizeDraggable === false) return;
21
+
22
+ // 鼠标移动时计算draggedSizeValue的值
23
+ const { x, y } = e;
24
+
25
+ const maxHeight = document.documentElement.clientHeight;
26
+ const maxWidth = document.documentElement.clientWidth;
27
+ const offsetHeight = 8;
28
+ const offsetWidth = 8;
29
+ // x 轴方向使用最大宽度,y轴方向使用最大高度
30
+ const max = placement === "left" || placement === "right" ? maxWidth : maxHeight;
31
+ // x 轴方向使用默认最小宽度,y轴方向使用默认最小高度
32
+ const min = placement === "left" || placement === "right" ? offsetWidth : offsetHeight;
33
+
34
+ const { max: limitMax, min: limitMin } = getSizeDraggable(sizeDraggable, { max, min });
35
+
36
+ const moveSize = calcMoveSize(placement, {
37
+ x,
38
+ y,
39
+ maxWidth,
40
+ maxHeight,
41
+ max: limitMax,
42
+ min: limitMin
43
+ });
44
+
45
+ if (typeof moveSize === "undefined") return;
46
+ changeDragSizeValue(`${moveSize}px`);
47
+ dragSizeRef.current = moveSize;
48
+ },
49
+ [placement, sizeDraggable]
50
+ );
51
+
52
+ const draggableLineStyles: Styles = useMemo(() => {
53
+ // 设置拖拽control的样式
54
+ const isHorizontal = ["right", "left"].includes(placement);
55
+ const oppositeMap = {
56
+ left: "right",
57
+ right: "left",
58
+ top: "bottom",
59
+ bottom: "top"
60
+ };
61
+ return {
62
+ zIndex: 1,
63
+ position: "absolute",
64
+ background: "transparent",
65
+ [oppositeMap[placement]]: 0,
66
+ width: isHorizontal ? "16px" : "100%",
67
+ height: isHorizontal ? "100%" : "16px",
68
+ cursor: isHorizontal ? "col-resize" : "row-resize"
69
+ };
70
+ }, [placement]);
71
+
72
+ const handleMouseup = useCallback(
73
+ (e: MouseEvent) => {
74
+ document.removeEventListener("mouseup", handleMouseup, true);
75
+ document.removeEventListener("mousemove", handleMousemove, true);
76
+ onSizeDragEnd?.({
77
+ e,
78
+ // 此处不要使用 dragSizeValue,useState 的更新是异步的,在鼠标拖拽的同步操作中取不到最新的值
79
+ size: dragSizeRef.current
80
+ });
81
+ toggleSizeDragging(false);
82
+ },
83
+ [handleMousemove, onSizeDragEnd]
84
+ );
85
+
86
+ const enableDrag = useCallback(() => {
87
+ // mousedown 绑定 mousemove 和 mouseup 事件
88
+ document.addEventListener("mouseup", handleMouseup, true);
89
+ document.addEventListener("mousemove", handleMousemove, true);
90
+ toggleSizeDragging(true);
91
+ }, [handleMousemove, handleMouseup]);
92
+
93
+ const draggingStyles: Styles = isSizeDragging ? { userSelect: "none" } : {};
94
+
95
+ return { dragSizeValue, enableDrag, draggableLineStyles, draggingStyles };
96
+ };
97
+
98
+ export default useDrag;
@@ -0,0 +1,36 @@
1
+ import { useRef, useCallback, useLayoutEffect } from "react";
2
+ let key = 1;
3
+ export const useLockStyle = (props) => {
4
+ const { visible } = props;
5
+ const drawerLockStyleRef = useRef<HTMLStyleElement>(null);
6
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
7
+ const clearStyleFunc = useCallback(() => {
8
+ console.log("clearStyleFunc");
9
+ clearTimeout(timerRef.current);
10
+ timerRef.current = setTimeout(() => {
11
+ drawerLockStyleRef.current.parentNode?.removeChild?.(drawerLockStyleRef.current);
12
+ }, 150);
13
+ }, []);
14
+
15
+ useLayoutEffect(() => {
16
+ if (typeof document === "undefined" || !visible) return;
17
+ if (!drawerLockStyleRef.current) {
18
+ drawerLockStyleRef.current = document.createElement("style");
19
+ }
20
+ drawerLockStyleRef.current.dataset.id = `td_drawer_${+new Date()}_${key++}`;
21
+ drawerLockStyleRef.current.innerHTML = `
22
+ html body {
23
+ overflow-y: hidden;
24
+ }
25
+ `;
26
+ }, [visible]);
27
+
28
+ useLayoutEffect(() => {
29
+ if (typeof document === "undefined") return;
30
+ if (visible) {
31
+ if (drawerLockStyleRef.current) document.head.appendChild(drawerLockStyleRef.current);
32
+ } else {
33
+ clearStyleFunc();
34
+ }
35
+ }, [visible, clearStyleFunc]);
36
+ };
@@ -0,0 +1,5 @@
1
+ import _Drawer from "./Drawer";
2
+ import "./style/index";
3
+ export type { TdDrawerProps } from "./type";
4
+ export const Drawer = _Drawer;
5
+ export default Drawer;
@@ -0,0 +1 @@
1
+ import "./index.css";
@@ -0,0 +1 @@
1
+ import "../../styles/components/drawer/_index.scss";
package/drawer/type.ts ADDED
@@ -0,0 +1,193 @@
1
+ import { ButtonProps } from "../button";
2
+ import { TNode, Styles, AttachNode } from "../common";
3
+ import { MouseEvent, KeyboardEvent } from "react";
4
+ export interface TdDrawerProps {
5
+ /**
6
+ * 抽屉挂载的节点,默认挂在组件本身的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body
7
+ */
8
+ attach?: AttachNode;
9
+ /**
10
+ * 抽屉内容
11
+ */
12
+ body?: TNode;
13
+ /**
14
+ * 取消按钮,可自定义。值为 null 则不显示取消按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制取消事件
15
+ */
16
+ cancelBtn?: FooterButton;
17
+ /**
18
+ * 抽屉内容,同 body
19
+ */
20
+ children?: TNode;
21
+ /**
22
+ * 关闭按钮,可以自定义。值为 true 显示默认关闭按钮,值为 false 不显示关闭按钮。值类型为 string 则直接显示值,如:“关闭”。值类型为 TNode,则表示呈现自定义按钮示例
23
+ */
24
+ closeBtn?: TNode;
25
+ /**
26
+ * 按下 ESC 时是否触发抽屉关闭事件
27
+ */
28
+ closeOnEscKeydown?: boolean;
29
+ /**
30
+ * 点击蒙层时是否触发抽屉关闭事件
31
+ */
32
+ closeOnOverlayClick?: boolean;
33
+ /**
34
+ * 取消按钮,可自定义。值为 null 则不显示确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制确认事件
35
+ */
36
+ confirmBtn?: FooterButton;
37
+ /**
38
+ * 抽屉关闭时是否销毁节点
39
+ * @default false
40
+ */
41
+ destroyOnClose?: boolean;
42
+ /**
43
+ * 底部操作栏,默认会有“确认”和“取消”两个按钮。值为 true 显示默认操作按钮,值为 false 或 null 不显示任何内容,值类型为 TNode 表示自定义底部内容
44
+ * @default true
45
+ */
46
+ footer?: TNode;
47
+ /**
48
+ * 是否强制渲染Drawer
49
+ * @default false
50
+ */
51
+ forceRender?: boolean;
52
+ /**
53
+ * 头部内容。值为 true 显示空白头部,值为 false 不显示头部,值类型为 string 则直接显示值,值类型为 TNode 表示自定义头部内容
54
+ * @default true
55
+ */
56
+ header?: TNode;
57
+ /**
58
+ * 是否启用抽屉懒加载,启用时抽屉的内容不渲染
59
+ * @default false
60
+ */
61
+ lazy?: boolean;
62
+ /**
63
+ * 展开方式,有两种:直接展示在内容上方 和 推开内容区域
64
+ * @default overlay
65
+ */
66
+ mode?: "overlay" | "push";
67
+ /**
68
+ * 抽屉方向
69
+ * @default right
70
+ */
71
+ placement?: "left" | "right" | "top" | "bottom";
72
+ /**
73
+ * 防止滚动穿透
74
+ * @default true
75
+ */
76
+ preventScrollThrough?: boolean;
77
+ /**
78
+ * 仅在挂载元素中显示抽屉,默认在浏览器可视区域显示。父元素需要有定位属性,如:position: relative
79
+ * @default false
80
+ */
81
+ showInAttachedElement?: boolean;
82
+ /**
83
+ * 是否显示遮罩层
84
+ * @default true
85
+ */
86
+ showOverlay?: boolean;
87
+ /**
88
+ * 尺寸,支持 'small', 'medium', 'large','35px', '30%', '3em' 等。纵向抽屉调整的是抽屉宽度,横向抽屉调整的是抽屉高度
89
+ */
90
+ size?: string;
91
+ /**
92
+ * 抽屉大小可拖拽调整,横向抽屉调整宽度
93
+ * @default false
94
+ */
95
+ sizeDraggable?: boolean | SizeDragLimit;
96
+ /**
97
+ * 组件是否可见
98
+ * @default false
99
+ */
100
+ visible?: boolean;
101
+ /**
102
+ * 抽屉层级,样式默认为 1500
103
+ */
104
+ zIndex?: number;
105
+ /**
106
+ * 对话框执行消失动画效果前触发
107
+ */
108
+ onBeforeClose?: () => void;
109
+ /**
110
+ * 对话框执行弹出动画效果前触发
111
+ */
112
+ onBeforeOpen?: () => void;
113
+ /**
114
+ * 如果“取消”按钮存在,点击“取消”按钮时触发,同时触发关闭事件
115
+ */
116
+ onCancel?: (context: { e: MouseEvent<HTMLDivElement | HTMLButtonElement> }) => void;
117
+ /**
118
+ * 关闭事件,取消按钮点击时、关闭按钮点击时、ESC 按下时、点击蒙层时均会触发
119
+ */
120
+ onClose?: (context: DrawerCloseContext) => void;
121
+ /**
122
+ * 如果关闭按钮存在,点击关闭按钮时触发该事件,同时触发关闭事件
123
+ */
124
+ onCloseBtnClick?: (context: { e: MouseEvent<HTMLDivElement> }) => void;
125
+ /**
126
+ * 如果“确认”按钮存在,则点击“确认”按钮时触发
127
+ */
128
+ onConfirm?: (context: { e: MouseEvent<HTMLDivElement | HTMLButtonElement> }) => void;
129
+ /**
130
+ * 按下 ESC 键时触发
131
+ */
132
+ onEscKeydown?: (context: { e: KeyboardEvent<HTMLDivElement> }) => void;
133
+ /**
134
+ * 如果蒙层存在,点击蒙层时触发
135
+ */
136
+ onOverlayClick?: (context: { e: MouseEvent<HTMLDivElement> }) => void;
137
+ /**
138
+ * 抽屉大小调整结束事件
139
+ */
140
+ onSizeDragEnd?: (context: { e: globalThis.MouseEvent; size: number }) => void;
141
+ }
142
+
143
+ export interface DrawerOptions extends Omit<TdDrawerProps, "attach"> {
144
+ /**
145
+ * 抽屉挂载的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body
146
+ * @default 'body'
147
+ */
148
+ attach?: AttachNode;
149
+ /**
150
+ * 抽屉类名,示例:'t-class-drawer-first t-class-drawer-second'
151
+ * @default ''
152
+ */
153
+ className?: string;
154
+ /**
155
+ * 弹框 style 属性,输入 [CSSStyleDeclaration.cssText](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/cssText)
156
+ */
157
+ style?: Styles;
158
+ }
159
+
160
+ export interface DrawerInstance {
161
+ /**
162
+ * 销毁抽屉
163
+ */
164
+ destroy?: () => void;
165
+ /**
166
+ * 隐藏抽屉
167
+ */
168
+ hide?: () => void;
169
+ /**
170
+ * 显示抽屉
171
+ */
172
+ show?: () => void;
173
+ /**
174
+ * 更新抽屉内容
175
+ */
176
+ update?: (props: DrawerOptions) => void;
177
+ }
178
+
179
+ export type FooterButton = string | ButtonProps | TNode;
180
+
181
+ export interface SizeDragLimit {
182
+ max: number;
183
+ min: number;
184
+ }
185
+
186
+ export type DrawerEventSource = "esc" | "close-btn" | "cancel" | "overlay";
187
+
188
+ export interface DrawerCloseContext {
189
+ trigger: DrawerEventSource;
190
+ e: MouseEvent<HTMLDivElement | HTMLButtonElement> | KeyboardEvent<HTMLDivElement>;
191
+ }
192
+
193
+ export type DrawerMethod = (options?: DrawerOptions) => DrawerInstance;
@@ -0,0 +1,76 @@
1
+ type Placement = "left" | "right" | "top" | "bottom";
2
+
3
+ interface SizeDragLimit {
4
+ max: number;
5
+ min: number;
6
+ }
7
+
8
+ export function getSizeDraggable(sizeDraggable: boolean | SizeDragLimit, limit: { max: number; min: number }) {
9
+ if (typeof sizeDraggable === "boolean") {
10
+ return {
11
+ allowSizeDraggable: sizeDraggable,
12
+ max: limit.max,
13
+ min: limit.min
14
+ };
15
+ }
16
+
17
+ return {
18
+ allowSizeDraggable: true,
19
+ max: sizeDraggable.max,
20
+ min: sizeDraggable.min
21
+ };
22
+ }
23
+
24
+ type IOptions = {
25
+ x: number;
26
+ y: number;
27
+ maxWidth: number;
28
+ maxHeight: number;
29
+ min: number;
30
+ max: number;
31
+ };
32
+
33
+ // > min && < max
34
+ function calcSizeRange(size: number, min: number, max: number) {
35
+ return Math.min(Math.max(size, min), max);
36
+ }
37
+
38
+ export function calcMoveSize(placement: Placement, opts: IOptions) {
39
+ const { x, y, max, min, maxWidth, maxHeight } = opts;
40
+ let moveSize: number | undefined;
41
+ switch (placement) {
42
+ case "right":
43
+ // |<--- x --->| |
44
+ // | maxWidth |
45
+ // | size | > min && < max
46
+ moveSize = calcSizeRange(maxWidth - x, min, max);
47
+ break;
48
+ case "left":
49
+ // |<-- x -->| |
50
+ // x > min && < max
51
+ moveSize = calcSizeRange(x, min, max);
52
+ break;
53
+ case "top":
54
+ // - - - - - - - -
55
+ // | y |
56
+ // | |
57
+ // - - - - - - - -
58
+ // > min && < max
59
+ // moveSize = Math.min(Math.max(y, min), max);
60
+ moveSize = calcSizeRange(y, min, max);
61
+ break;
62
+ case "bottom":
63
+ // - - - - - - - -
64
+ // | y |
65
+ // | | maxHeight
66
+ // - - - - - - - -
67
+ // | size |
68
+ // > min && < max
69
+ moveSize = calcSizeRange(maxHeight - y, min, max);
70
+ break;
71
+ default:
72
+ // 参数缺失直接返回
73
+ return moveSize;
74
+ }
75
+ return moveSize;
76
+ }