@fibery/ui-kit 3.0.0 → 4.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 (64) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +14 -8
  3. package/src/actions-menu/actions-menu-item.tsx +1 -0
  4. package/src/actions-menu/actions-menu.tsx +1 -3
  5. package/src/antd/ant-modal.tsx +156 -7
  6. package/src/antd/ant-upload.tsx +285 -27
  7. package/src/antd/auto-complete.tsx +145 -0
  8. package/src/antd/auto-width-transparent-textarea.tsx +9 -2
  9. package/src/antd/global-overrides.ts +352 -0
  10. package/src/antd/index.tsx +3 -2
  11. package/src/antd/input-number.tsx +97 -11
  12. package/src/antd/input.tsx +127 -8
  13. package/src/antd/styles.ts +90 -32
  14. package/src/antd/tabs.tsx +139 -27
  15. package/src/avatar.tsx +2 -56
  16. package/src/color-adjuster.ts +24 -1
  17. package/src/count-badge.tsx +10 -1
  18. package/src/date-picker/date-range-picker.tsx +6 -6
  19. package/src/date-picker/single-date-picker.tsx +2 -3
  20. package/src/date-picker/types.ts +1 -1
  21. package/src/date-picker/with-popup-control.tsx +3 -9
  22. package/src/design-system/colors-callout.warm-dark.test.ts +22 -22
  23. package/src/design-system/colors-callout.warm-light.test.ts +19 -19
  24. package/src/design-system/colors-css.ts +1 -1
  25. package/src/design-system/colors-enum.dark.warm.test.ts +63 -63
  26. package/src/design-system/colors-enum.light.warm.test.ts +54 -54
  27. package/src/design-system/colors-highlight.warm-dark.test.ts +21 -21
  28. package/src/design-system/colors-highlight.warm-light.test.ts +21 -21
  29. package/src/design-system/colors.ts +1 -1
  30. package/src/design-system/fns.badge.dark.warm.test.ts +34 -34
  31. package/src/design-system/fns.badge.light.warm.test.ts +31 -31
  32. package/src/design-system/fns.deneutralize.test.ts +44 -0
  33. package/src/design-system/fns.icon.dark.warm.test.ts +21 -21
  34. package/src/design-system/fns.icon.light.warm.test.ts +17 -17
  35. package/src/design-system/fns.icon.ts +21 -13
  36. package/src/design-system/fns.ts +27 -3
  37. package/src/design-system/typography.ts +1 -1
  38. package/src/design-system.ts +1 -1
  39. package/src/fibermoji-placeholder.tsx +2 -3
  40. package/src/icons/icon.tsx +11 -1
  41. package/src/icons/types.ts +1 -1
  42. package/src/layout-styles.ts +5 -1
  43. package/src/link-input/components/ant-text-area-with-custom-read-state.tsx +2 -1
  44. package/src/media-query-utils.ts +4 -13
  45. package/src/palettes/inspect.defs.colors.warm-arch.test.ts +137 -137
  46. package/src/palettes/inspect.defs.colors.warm-user.test.ts +137 -137
  47. package/src/palettes/warm.ts +2 -0
  48. package/src/root-theme-provider.test.tsx +1 -1
  49. package/src/select/index.tsx +3 -3
  50. package/src/select/select-in-popover.tsx +34 -51
  51. package/src/select/select.tsx +2 -2
  52. package/src/theme-provider.test.tsx +1 -1
  53. package/src/theme-provider.tsx +3 -9
  54. package/src/theme-snapshots.test.ts +9 -13
  55. package/src/{theming/build.ts → theming.build.ts} +6 -9
  56. package/src/{theming/theming-fibery.snapshot.css → theming.generated.css} +52 -52
  57. package/src/{theming/theming-fibery.snapshot.ts → theming.generated.ts} +52 -52
  58. package/src/{themed-ink.tsx → type-label.tsx} +2 -5
  59. package/src/antd/auto-complete.d.ts +0 -2
  60. package/src/antd/auto-complete.ts +0 -37
  61. package/src/antd/tabs.d.ts +0 -5
  62. package/src/theming/index.ts +0 -20
  63. package/src/theming/theming-vzdbery.snapshot.css +0 -2001
  64. package/src/theming/theming-vzdbery.snapshot.ts +0 -2519
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @fibery/ui-kit
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 5025ffe: Inline antd dep monolith (tabs,input,select,autocomplete,upload) for -50kb gzipped impact
8
+
3
9
  ## 3.0.0
4
10
 
5
11
  ### Major Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "dependencies": {
@@ -10,7 +10,7 @@
10
10
  "@radix-ui/react-collapsible": "1.1.0",
11
11
  "@radix-ui/react-context-menu": "2.2.1",
12
12
  "@radix-ui/react-dropdown-menu": "2.1.1",
13
- "@radix-ui/react-focus-scope": "^1.1.8",
13
+ "@radix-ui/react-focus-scope": "1.1.8",
14
14
  "@radix-ui/react-navigation-menu": "1.2.1",
15
15
  "@radix-ui/react-slot": "1.2.4",
16
16
  "@radix-ui/react-toast": "1.2.2",
@@ -20,13 +20,12 @@
20
20
  "@tanstack/hotkeys": "0.7.1",
21
21
  "@tanstack/react-hotkeys": "0.9.1",
22
22
  "@types/apca-w3": "0.1.3",
23
- "@types/d3-shape": "^3.1.6",
24
- "@types/react-select-country-list": "^2.2.3",
25
- "@types/setimmediate": "^1.0.4",
26
- "antd": "4.24.7",
23
+ "@types/d3-shape": "3.1.6",
24
+ "@types/react-select-country-list": "2.2.3",
25
+ "@types/setimmediate": "1.0.4",
27
26
  "apca-w3": "0.1.9",
28
27
  "apcach": "github:antiflasher/apcach#54077b3",
29
- "chrono-node": "^2.9.0",
28
+ "chrono-node": "2.9.0",
30
29
  "classnames": "2.3.1",
31
30
  "cmdk": "1.0.0",
32
31
  "color-hash": "1.0.3",
@@ -41,8 +40,15 @@
41
40
  "photoswipe": "5.4.4",
42
41
  "popper-max-size-modifier": "0.2.0",
43
42
  "prop-types": "15.7.2",
43
+ "rc-dialog": "9.0.4",
44
+ "rc-input": "0.1.4",
44
45
  "rc-input-number": "8.5.0",
46
+ "rc-input-number-v7": "npm:rc-input-number@7.3.11",
45
47
  "rc-progress": "3.4.1",
48
+ "rc-select": "14.1.18",
49
+ "rc-tabs": "12.5.10",
50
+ "rc-textarea": "0.4.7",
51
+ "rc-upload": "4.3.4",
46
52
  "react-color": "2.19.3",
47
53
  "react-day-picker": "8.10.1",
48
54
  "react-intersection-observer": "9.3.5",
@@ -94,7 +100,7 @@
94
100
  "test:ci": "../../scripts/test.sh --ci",
95
101
  "test:coverage": "../../scripts/test.sh --ci --coverage",
96
102
  "check": "../../scripts/check.sh",
97
- "lint": "../../scripts/lint.sh",
103
+ "lint": "pnpm -w lint .",
98
104
  "generate-icons": "node scripts/generate-icons.mjs",
99
105
  "show-colors": "cd ../.. && SHOW_COLORS=1 ./node_modules/.bin/vitest run --project ui-kit --disable-console-intercept -t 'show all current colors'",
100
106
  "typecheck": "../../scripts/typecheck.sh"
@@ -79,6 +79,7 @@ const rowWithDescriptionCss = css`
79
79
  `;
80
80
 
81
81
  const rowContentCss = css`
82
+ min-width: 0;
82
83
  flex-grow: 1;
83
84
  `;
84
85
 
@@ -4,7 +4,6 @@ import {css, cx} from "@linaria/core";
4
4
  import {useEffect, useState} from "react";
5
5
 
6
6
  import * as DropdownMenu from "../dropdown-menu";
7
- import {useFeatures} from "../features";
8
7
  import * as ModalMenu from "../modal-menu";
9
8
  import {useOpenPopoverTracker} from "../open-popover-tracker";
10
9
  import {useIsPhone} from "../use-is-phone";
@@ -47,7 +46,6 @@ export const ActionsMenu = ({
47
46
  sticky,
48
47
  height,
49
48
  }: ActionsMenuProps): JSX.Element => {
50
- const features = useFeatures<{enableMobileMenus: boolean}>();
51
49
  const [isOpen = false, setOpen] = useControllableState({value: open, onChange: onOpenChange});
52
50
  const isPhone = useIsPhone();
53
51
  const [confirmation, setConfirmation] = useState<ActionsMenuConfirmationProps | null>(null);
@@ -58,7 +56,7 @@ export const ActionsMenu = ({
58
56
  return () => onOpenPopoverChange(false);
59
57
  }, [isOpen, onOpenPopoverChange]);
60
58
 
61
- const MenuPrimitive = isPhone && features.enableMobileMenus && supportsMobile ? ModalMenu : DropdownMenu;
59
+ const MenuPrimitive = isPhone && supportsMobile ? ModalMenu : DropdownMenu;
62
60
 
63
61
  return (
64
62
  <ActionsMenuContextProvider
@@ -1,32 +1,181 @@
1
1
  import {css, cx} from "@linaria/core";
2
- import Modal from "antd/lib/modal";
3
- import "antd/lib/modal/style/css";
4
- import type {ModalProps} from "antd/lib/modal/Modal";
5
- import type {ReactNode} from "react";
2
+ import Dialog from "rc-dialog";
3
+ import type {IDialogPropTypes} from "rc-dialog/lib/IDialogPropTypes";
4
+ import type {CSSProperties, MouseEvent as ReactMouseEvent, ReactNode} from "react";
6
5
 
7
- import {border, space, themeVars} from "../design-system";
6
+ import {border, space, themeVars, transitions} from "../design-system";
7
+ import CloseIcon from "../icons/react/close";
8
8
  import {ThemeProvider} from "../theme-provider";
9
9
 
10
+ const defaultCloseIcon = <CloseIcon />;
11
+
10
12
  const modalCss = css`
13
+ & .ant-modal-mask {
14
+ position: fixed;
15
+ inset: 0;
16
+ z-index: 1000;
17
+ height: 100%;
18
+ background-color: ${themeVars.modalBg};
19
+ }
20
+
21
+ & .ant-modal-wrap {
22
+ position: fixed;
23
+ inset: 0;
24
+ z-index: 1000;
25
+ overflow: auto;
26
+ outline: 0;
27
+ -webkit-overflow-scrolling: touch;
28
+ }
29
+
30
+ & .ant-modal {
31
+ position: relative;
32
+ top: 100px;
33
+ margin: 0 auto;
34
+ padding-bottom: 24px;
35
+ pointer-events: none;
36
+ }
37
+
38
+ & .ant-modal-centered {
39
+ text-align: center;
40
+ }
41
+
42
+ & .ant-modal-centered::before {
43
+ display: inline-block;
44
+ width: 0;
45
+ height: 100%;
46
+ vertical-align: middle;
47
+ content: "";
48
+ }
49
+
50
+ & .ant-modal-centered .ant-modal {
51
+ top: 0;
52
+ display: inline-block;
53
+ padding-bottom: 0;
54
+ text-align: left;
55
+ vertical-align: middle;
56
+ }
57
+
11
58
  & .ant-modal-content {
59
+ position: relative;
12
60
  box-shadow: ${themeVars.shadowPopup};
13
61
  margin-left: ${space.s6}px;
14
62
  margin-right: ${space.s6}px;
15
63
  background-color: ${themeVars.modalContentBg};
16
64
  border-radius: ${border.radius8}px;
65
+ pointer-events: auto;
17
66
  }
18
67
 
19
68
  & .ant-modal-body {
20
69
  padding: ${space.s20}px;
21
70
  }
71
+
72
+ & .ant-modal-close {
73
+ position: absolute;
74
+ top: 0;
75
+ right: 0;
76
+ z-index: 10;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ width: 56px;
81
+ height: 56px;
82
+ padding: 0;
83
+ color: ${themeVars.iconColor};
84
+ background: transparent;
85
+ border: 0;
86
+ outline: 0;
87
+ cursor: pointer;
88
+ }
89
+
90
+ & .ant-fade-enter,
91
+ & .ant-fade-appear {
92
+ opacity: 0;
93
+ }
94
+
95
+ & .ant-fade-enter-active,
96
+ & .ant-fade-appear-active {
97
+ opacity: 1;
98
+ transition: opacity ${transitions.normal};
99
+ }
100
+
101
+ & .ant-fade-leave {
102
+ opacity: 1;
103
+ }
104
+
105
+ & .ant-fade-leave-active {
106
+ opacity: 0;
107
+ transition: opacity ${transitions.normal};
108
+ }
109
+
110
+ & .ant-zoom-enter,
111
+ & .ant-zoom-appear {
112
+ opacity: 0;
113
+ transform: scale(0.96);
114
+ }
115
+
116
+ & .ant-zoom-enter-active,
117
+ & .ant-zoom-appear-active {
118
+ opacity: 1;
119
+ transform: scale(1);
120
+ transition:
121
+ opacity ${transitions.normal},
122
+ transform ${transitions.normal};
123
+ }
124
+
125
+ & .ant-zoom-leave {
126
+ opacity: 1;
127
+ transform: scale(1);
128
+ }
129
+
130
+ & .ant-zoom-leave-active {
131
+ opacity: 0;
132
+ transform: scale(0.96);
133
+ transition:
134
+ opacity ${transitions.normal},
135
+ transform ${transitions.normal};
136
+ }
22
137
  `;
23
138
 
24
139
  function renderWithTheme(node: ReactNode): ReactNode {
25
140
  return <ThemeProvider portal>{node}</ThemeProvider>;
26
141
  }
27
142
 
28
- export function AntModal({bodyStyle = {}, className, ...restProps}: ModalProps) {
143
+ export type AntModalProps = Omit<IDialogPropTypes, "visible" | "onClose" | "prefixCls"> & {
144
+ open?: boolean;
145
+ onCancel?: (e: ReactMouseEvent<HTMLElement>) => void;
146
+ centered?: boolean;
147
+ bodyStyle?: CSSProperties;
148
+ };
149
+
150
+ export function AntModal({
151
+ open,
152
+ onCancel,
153
+ centered,
154
+ bodyStyle = {},
155
+ className,
156
+ wrapClassName,
157
+ rootClassName,
158
+ width = 520,
159
+ transitionName = "ant-zoom",
160
+ maskTransitionName = "ant-fade",
161
+ closeIcon = defaultCloseIcon,
162
+ ...restProps
163
+ }: AntModalProps) {
29
164
  return (
30
- <Modal bodyStyle={bodyStyle} className={cx(className, modalCss)} modalRender={renderWithTheme} {...restProps} />
165
+ <Dialog
166
+ prefixCls="ant-modal"
167
+ visible={open}
168
+ onClose={onCancel as IDialogPropTypes["onClose"]}
169
+ bodyStyle={bodyStyle}
170
+ className={className}
171
+ wrapClassName={cx(wrapClassName, centered && "ant-modal-centered")}
172
+ rootClassName={cx(rootClassName, modalCss)}
173
+ width={width}
174
+ transitionName={transitionName}
175
+ maskTransitionName={maskTransitionName}
176
+ closeIcon={closeIcon}
177
+ modalRender={renderWithTheme}
178
+ {...restProps}
179
+ />
31
180
  );
32
181
  }
@@ -1,21 +1,56 @@
1
1
  import {css, cx} from "@linaria/core";
2
- import type {UploadProps} from "antd/lib/upload";
3
- import Upload from "antd/lib/upload";
4
- import type {UploadFile} from "antd/lib/upload/interface";
5
- import type {ComponentPropsWithoutRef, FunctionComponent, PropsWithChildren} from "react";
6
- import "antd/lib/upload/style/css";
7
- import {forwardRef} from "react";
8
-
9
- import {border, space, themeVars, transition} from "../design-system";
10
- import {wrapWith} from "./utils";
11
-
12
- function getWrapper(wrapperClassName: string) {
13
- return forwardRef<HTMLDivElement, ComponentPropsWithoutRef<"div">>(function UploadWrapper(
14
- {className, ...props},
15
- ref: React.ForwardedRef<HTMLDivElement>
16
- ) {
17
- return <div className={cx(className, wrapperClassName)} ref={ref} {...props} />;
18
- });
2
+ import RcUpload from "rc-upload";
3
+ import type {RcFile, UploadProps as RcUploadProps, UploadRequestOption} from "rc-upload/lib/interface";
4
+ import type {ComponentPropsWithoutRef} from "react";
5
+ import {forwardRef, useCallback, useRef, useState} from "react";
6
+
7
+ import {border, space, textStyles, themeVars, transitions} from "../design-system";
8
+
9
+ export type UploadFileStatus = "error" | "success" | "done" | "uploading" | "removed";
10
+
11
+ export interface UploadFile<T = unknown> {
12
+ uid: string;
13
+ size?: number;
14
+ name: string;
15
+ lastModified?: number;
16
+ lastModifiedDate?: Date;
17
+ url?: string;
18
+ status?: UploadFileStatus;
19
+ percent?: number;
20
+ thumbUrl?: string;
21
+ originFileObj?: RcFile;
22
+ response?: T;
23
+ error?: unknown;
24
+ type?: string;
25
+ xhr?: T;
26
+ }
27
+
28
+ export interface UploadChangeParam<T = UploadFile> {
29
+ file: T;
30
+ fileList: T[];
31
+ event?: {percent: number};
32
+ }
33
+
34
+ function file2Obj(file: RcFile): UploadFile {
35
+ return {
36
+ uid: file.uid,
37
+ name: file.name,
38
+ size: file.size,
39
+ type: file.type,
40
+ lastModified: file.lastModified,
41
+ percent: 0,
42
+ originFileObj: file,
43
+ };
44
+ }
45
+
46
+ function upsertFile(file: UploadFile, list: UploadFile[]): UploadFile[] {
47
+ const index = list.findIndex((f) => f.uid === file.uid);
48
+ if (index === -1) {
49
+ return [...list, file];
50
+ }
51
+ const next = list.slice();
52
+ next[index] = file;
53
+ return next;
19
54
  }
20
55
 
21
56
  export const antUploadStyle = css`
@@ -29,12 +64,50 @@ export const antUploadStyle = css`
29
64
  alignItems: "stretch",
30
65
  alignContent: "stretch",
31
66
  },
32
- "& .ant-upload:where(.ant-upload-drag), .ant-upload:where(.ant-upload-select-picture-card)": {
67
+ "& .ant-upload": {
68
+ boxSizing: "border-box",
69
+ margin: 0,
70
+ padding: 0,
71
+ ...textStyles.regular,
72
+ color: themeVars.textColor,
73
+ listStyle: "none",
74
+ outline: "none",
75
+ },
76
+ "& .ant-upload p": {
77
+ margin: 0,
78
+ },
79
+ "& .ant-upload-btn": {
80
+ display: "block",
81
+ width: "100%",
82
+ outline: "none",
83
+ },
84
+ "& .ant-upload input[type='file']": {
85
+ cursor: "pointer",
86
+ },
87
+ "& .ant-upload.ant-upload-disabled": {
88
+ color: themeVars.disabledTextColor,
89
+ cursor: "not-allowed",
90
+ },
91
+ "& .ant-upload.ant-upload-disabled input[type='file']": {
92
+ cursor: "not-allowed",
93
+ },
94
+ "& .ant-upload-select": {
95
+ display: "inline-flex",
96
+ alignItems: "stretch",
97
+ maxWidth: "100%",
98
+ },
99
+ "& .ant-upload-select > .ant-upload": {
100
+ display: "inline-flex",
101
+ alignItems: "center",
102
+ outline: "none",
103
+ },
104
+ "& .ant-upload:where(.ant-upload-drag), & .ant-upload:where(.ant-upload-select-picture-card)": {
33
105
  border: `1px dashed ${themeVars.colorAccentBgStrong}`,
34
106
  position: "relative",
35
107
  backgroundColor: themeVars.transparent,
36
108
  borderRadius: border.radius6,
37
- transition: `border-color ${transition}`,
109
+ transition: `border-color ${transitions.faster}`,
110
+ cursor: "pointer",
38
111
  margin: 0,
39
112
  flex: 1,
40
113
  alignSelf: "stretch",
@@ -57,19 +130,204 @@ export const antUploadStyle = css`
57
130
  alignContent: "stretch",
58
131
  padding: space.s8,
59
132
  },
60
- "&.ant-upload-drag-hover": {
61
- border: `1px dashed ${themeVars.colorAccent}`,
133
+ "&.ant-upload-drag-hover:not(.ant-upload-disabled)": {
134
+ border: `1px dashed ${themeVars.colorAccentStrokeFocus}`,
62
135
  transition: "none",
63
136
  },
64
- "&:hover": {
65
- border: `1px dashed ${themeVars.colorAccent}`,
137
+ "&:not(.ant-upload-disabled):hover": {
138
+ border: `1px dashed ${themeVars.colorAccentStrokeHover}`,
66
139
  transition: "none",
67
140
  },
68
141
  },
142
+ "& .ant-upload.ant-upload-drag .ant-upload-btn": {
143
+ display: "table",
144
+ height: "100%",
145
+ },
146
+ "& .ant-upload.ant-upload-drag .ant-upload-drag-container": {
147
+ display: "table-cell",
148
+ verticalAlign: "middle",
149
+ },
150
+ "& .ant-upload-picture-card-wrapper": {
151
+ display: "inline-block",
152
+ width: "100%",
153
+ },
154
+ "& .ant-upload-picture-card-wrapper::before": {
155
+ display: "table",
156
+ content: "''",
157
+ },
158
+ "& .ant-upload-picture-card-wrapper::after": {
159
+ display: "table",
160
+ clear: "both",
161
+ content: "''",
162
+ },
69
163
  }}
70
164
  `;
71
165
 
72
- export const AntUpload = wrapWith(getWrapper(antUploadStyle), Upload) as FunctionComponent<
73
- PropsWithChildren<UploadProps>
74
- >;
75
- export type {UploadFile};
166
+ export type AntUploadProps = Omit<
167
+ RcUploadProps,
168
+ "prefixCls" | "onStart" | "onSuccess" | "onError" | "onProgress" | "type" | "onChange" | "children"
169
+ > & {
170
+ type?: "select" | "drag";
171
+ listType?: "text" | "picture" | "picture-card";
172
+ disabled?: boolean;
173
+ className?: string;
174
+ wrapClassName?: string;
175
+ children?: React.ReactNode;
176
+ onChange?: (info: UploadChangeParam) => void;
177
+ };
178
+
179
+ function Wrapper({className, ...props}: ComponentPropsWithoutRef<"div">) {
180
+ return <div className={cx(className, antUploadStyle)} {...props} />;
181
+ }
182
+
183
+ export const AntUpload = forwardRef<HTMLSpanElement, AntUploadProps>(function AntUpload(
184
+ {type = "select", listType = "text", disabled, className, wrapClassName, children, onChange, ...rcProps},
185
+ ref
186
+ ) {
187
+ const [fileList, setFileList] = useState<UploadFile[]>([]);
188
+ const fileListRef = useRef(fileList);
189
+ fileListRef.current = fileList;
190
+ const [dragState, setDragState] = useState<"drop" | "dragover" | "dragleave">("drop");
191
+
192
+ const emit = useCallback(
193
+ (file: UploadFile, nextList: UploadFile[], event?: {percent: number}) => {
194
+ fileListRef.current = nextList;
195
+ setFileList(nextList);
196
+ onChange?.({file, fileList: nextList, event});
197
+ },
198
+ [onChange]
199
+ );
200
+
201
+ const handleStart = useCallback(
202
+ (file: RcFile) => {
203
+ const obj = file2Obj(file);
204
+ obj.status = "uploading";
205
+ emit(obj, upsertFile(obj, fileListRef.current));
206
+ },
207
+ [emit]
208
+ );
209
+
210
+ const handleProgress = useCallback(
211
+ (event: {percent?: number}, file: RcFile) => {
212
+ const current = fileListRef.current.find((f) => f.uid === file.uid);
213
+ if (!current) {
214
+ return;
215
+ }
216
+ const next = {...current, status: "uploading" as UploadFileStatus, percent: event.percent};
217
+ emit(next, upsertFile(next, fileListRef.current), {percent: event.percent ?? 0});
218
+ },
219
+ [emit]
220
+ );
221
+
222
+ const handleSuccess = useCallback(
223
+ (response: unknown, file: RcFile, xhr?: XMLHttpRequest) => {
224
+ const current = fileListRef.current.find((f) => f.uid === file.uid);
225
+ if (!current) {
226
+ return;
227
+ }
228
+ let parsed = response;
229
+ if (typeof response === "string") {
230
+ try {
231
+ parsed = JSON.parse(response);
232
+ } catch {
233
+ /* ignore */
234
+ }
235
+ }
236
+ const next: UploadFile = {
237
+ ...current,
238
+ status: "done",
239
+ percent: 100,
240
+ response: parsed,
241
+ xhr,
242
+ };
243
+ emit(next, upsertFile(next, fileListRef.current));
244
+ },
245
+ [emit]
246
+ );
247
+
248
+ const handleError = useCallback(
249
+ (error: Error, response: unknown, file: RcFile) => {
250
+ const current = fileListRef.current.find((f) => f.uid === file.uid);
251
+ if (!current) {
252
+ return;
253
+ }
254
+ const next: UploadFile = {
255
+ ...current,
256
+ status: "error",
257
+ error,
258
+ response,
259
+ };
260
+ emit(next, upsertFile(next, fileListRef.current));
261
+ },
262
+ [emit]
263
+ );
264
+
265
+ const onFileDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
266
+ setDragState(e.type as typeof dragState);
267
+ }, []);
268
+
269
+ const rcUploadProps: RcUploadProps = {
270
+ ...rcProps,
271
+ prefixCls: "ant-upload",
272
+ disabled,
273
+ onStart: handleStart,
274
+ onProgress: handleProgress,
275
+ onSuccess: handleSuccess,
276
+ onError: handleError,
277
+ };
278
+
279
+ if (type === "drag") {
280
+ const hasUploading = fileList.some((f) => f.status === "uploading");
281
+ const dragCls = cx(
282
+ "ant-upload",
283
+ "ant-upload-drag",
284
+ hasUploading && "ant-upload-drag-uploading",
285
+ dragState === "dragover" && "ant-upload-drag-hover",
286
+ disabled && "ant-upload-disabled",
287
+ className
288
+ );
289
+ return (
290
+ <Wrapper className={wrapClassName}>
291
+ <span ref={ref}>
292
+ <div className={dragCls} onDrop={onFileDrop} onDragOver={onFileDrop} onDragLeave={onFileDrop}>
293
+ <RcUpload {...rcUploadProps} className="ant-upload-btn">
294
+ <div className="ant-upload-drag-container">{children}</div>
295
+ </RcUpload>
296
+ </div>
297
+ </span>
298
+ </Wrapper>
299
+ );
300
+ }
301
+
302
+ const buttonCls = cx(
303
+ "ant-upload",
304
+ "ant-upload-select",
305
+ `ant-upload-select-${listType}`,
306
+ disabled && "ant-upload-disabled"
307
+ );
308
+ const button = (
309
+ <div className={buttonCls}>
310
+ <RcUpload {...rcUploadProps}>{children}</RcUpload>
311
+ </div>
312
+ );
313
+
314
+ if (listType === "picture-card") {
315
+ return (
316
+ <Wrapper className={wrapClassName}>
317
+ <span ref={ref} className={cx("ant-upload-picture-card-wrapper", className)}>
318
+ {button}
319
+ </span>
320
+ </Wrapper>
321
+ );
322
+ }
323
+
324
+ return (
325
+ <Wrapper className={wrapClassName}>
326
+ <span ref={ref} className={className}>
327
+ {button}
328
+ </span>
329
+ </Wrapper>
330
+ );
331
+ });
332
+
333
+ export type {UploadRequestOption};