diff --git a/.dumi/pages/index/components/MainBanner.tsx b/.dumi/pages/index/components/MainBanner.tsx
index 8eaa6a8c..ea11533e 100644
--- a/.dumi/pages/index/components/MainBanner.tsx
+++ b/.dumi/pages/index/components/MainBanner.tsx
@@ -1,11 +1,12 @@
import { createStyles } from 'antd-style';
import classnames from 'classnames';
-import { Link, useLocation } from 'dumi';
+import { useLocation } from 'dumi';
import React from 'react';
import { Button } from 'antd';
import useLocale from '../../../hooks/useLocale';
import useLottie from '../../../hooks/useLottie';
+import Link from '../../../theme/common/Link';
import { getLocalizedPathname, isZhCN } from '../../../theme/utils';
import Container from '../common/Container';
import SiteContext from './SiteContext';
diff --git a/.dumi/theme/builtins/ComponentOverview/index.tsx b/.dumi/theme/builtins/ComponentOverview/index.tsx
index e62a43b5..b730f9c5 100644
--- a/.dumi/theme/builtins/ComponentOverview/index.tsx
+++ b/.dumi/theme/builtins/ComponentOverview/index.tsx
@@ -243,7 +243,7 @@ const Overview: React.FC = () => {
{cardContent}
) : (
-
+
{cardContent}
);
diff --git a/.dumi/theme/builtins/DemoWrapper/index.tsx b/.dumi/theme/builtins/DemoWrapper/index.tsx
index 15816017..92f19f18 100644
--- a/.dumi/theme/builtins/DemoWrapper/index.tsx
+++ b/.dumi/theme/builtins/DemoWrapper/index.tsx
@@ -1,9 +1,9 @@
-import React, { useContext } from 'react';
import { BugOutlined, CodeOutlined, ExperimentOutlined } from '@ant-design/icons';
import { XProvider } from '@ant-design/x';
-import { Tooltip, Button } from 'antd';
+import { Button, Tooltip } from 'antd';
import classNames from 'classnames';
import { DumiDemoGrid, FormattedMessage } from 'dumi';
+import React, { Suspense, useContext } from 'react';
import useLayoutState from '../../../hooks/useLayoutState';
import useLocale from '../../../hooks/useLocale';
@@ -106,7 +106,9 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
-
+
+
+
);
diff --git a/.dumi/theme/builtins/LocaleLink/index.tsx b/.dumi/theme/builtins/LocaleLink/index.tsx
index 3086392d..692e2539 100644
--- a/.dumi/theme/builtins/LocaleLink/index.tsx
+++ b/.dumi/theme/builtins/LocaleLink/index.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
-import { Link } from 'dumi';
import useLocale from '../../../hooks/useLocale';
+import Link from '../../../theme/common/Link';
type LinkProps = Parameters[0];
diff --git a/.dumi/theme/common/Link.tsx b/.dumi/theme/common/Link.tsx
index 7d84c205..dd855110 100644
--- a/.dumi/theme/common/Link.tsx
+++ b/.dumi/theme/common/Link.tsx
@@ -1,7 +1,6 @@
-import { Link as DumiLink, useLocation, useNavigate } from 'dumi';
-import nprogress from 'nprogress';
+import { Link as DumiLink, useAppData, useLocation, useNavigate } from 'dumi';
import type { MouseEvent, MouseEventHandler } from 'react';
-import React, { forwardRef, useLayoutEffect, useTransition } from 'react';
+import React, { useMemo, forwardRef } from 'react';
export interface LinkProps {
to: string | { pathname?: string; search?: string; hash?: string };
@@ -9,64 +8,49 @@ export interface LinkProps {
className?: string;
onClick?: MouseEventHandler;
component?: React.ComponentType;
+ children?: React.ReactNode;
}
-nprogress.configure({ showSpinner: false });
-
-const Link = forwardRef>((props, ref) => {
- const { to, children, component, ...rest } = props;
- const [isPending, startTransition] = useTransition();
- const navigate = useNavigate();
- const { pathname } = useLocation();
-
- const href = React.useMemo(() => {
- if (typeof to === 'object') {
- return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
- }
- return to;
- }, [to]);
-
- const handleClick = (e: MouseEvent) => {
- props.onClick?.(e);
- if (!href?.startsWith('http')) {
- // Should support open in new tab
- if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
- e.preventDefault();
- startTransition(() => {
- if (href) {
- navigate(href);
- }
- });
+const Link = forwardRef>(
+ ({ component, children, to, ...rest }, ref) => {
+ const { pathname } = useLocation();
+ const { preloadRoute } = useAppData();
+ const navigate = useNavigate();
+ const href = useMemo(() => {
+ if (typeof to === 'object') {
+ return `${to.pathname || pathname}${to.search || ''}${to.hash || ''}`;
}
+ return to;
+ }, [to]);
+ const onClick = (e: MouseEvent) => {
+ rest.onClick?.(e);
+ if (!href?.startsWith('http')) {
+ // Should support open in new tab
+ if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
+ e.preventDefault();
+ navigate(href);
+ }
+ }
+ };
+ if (component) {
+ return React.createElement(
+ component,
+ {
+ ...rest,
+ ref,
+ href,
+ onClick,
+ onMouseEnter: () => preloadRoute?.(href),
+ },
+ children,
+ );
}
- };
-
- useLayoutEffect(() => {
- if (isPending) {
- nprogress.start();
- } else {
- nprogress.done();
- }
- }, [isPending]);
-
- if (component) {
- return React.createElement(
- component,
- {
- ...rest,
- ref,
- onClick: handleClick,
- href,
- },
- children,
+ return (
+
+ {children}
+
);
- }
-
- return (
-
- {children}
-
- );
-});
+ },
+);
export default Link;
diff --git a/.dumi/theme/slots/Footer/index.tsx b/.dumi/theme/slots/Footer/index.tsx
index fef42b1c..7d6c3418 100644
--- a/.dumi/theme/slots/Footer/index.tsx
+++ b/.dumi/theme/slots/Footer/index.tsx
@@ -15,13 +15,14 @@ import {
import { TinyColor } from '@ctrl/tinycolor';
import { createStyles } from 'antd-style';
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
-import { FormattedMessage, Link } from 'dumi';
+import { FormattedMessage } from 'dumi';
import RcFooter from 'rc-footer';
import type { FooterColumn } from 'rc-footer/lib/column';
import React, { useContext } from 'react';
import useLocale from '../../../hooks/useLocale';
import useLocation from '../../../hooks/useLocation';
+import Link from '../../../theme/common/Link';
import SiteContext from '../SiteContext';
import AdditionalInfo from './AdditionalInfo';
diff --git a/.dumirc.ts b/.dumirc.ts
index 5c2e244f..a3cdd5b8 100644
--- a/.dumirc.ts
+++ b/.dumirc.ts
@@ -8,7 +8,11 @@ import { version } from './package.json';
export default defineConfig({
plugins: ['dumi-plugin-color-chunk'],
+
+ // For
+ routePrefetch: {},
manifest: {},
+
conventionRoutes: {
// to avoid generate routes for .dumi/pages/index/components/xx
exclude: [/index\/components\//],
diff --git a/bun.lockb b/bun.lockb
index 96a35dd4..0707bedf 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/bubble/Bubble.tsx b/components/bubble/Bubble.tsx
index 1ef00ed6..8591bb5f 100644
--- a/components/bubble/Bubble.tsx
+++ b/components/bubble/Bubble.tsx
@@ -4,6 +4,8 @@ import React from 'react';
import { Avatar } from 'antd';
import useXComponentConfig from '../_util/hooks/use-x-component-config';
import { useXProviderContext } from '../x-provider';
+import Editor from './Editor';
+import useMergedConfig from './hooks/useMergedConfig';
import useTypedEffect from './hooks/useTypedEffect';
import useTypingConfig from './hooks/useTypingConfig';
import type { BubbleProps } from './interface';
@@ -40,6 +42,7 @@ const Bubble: React.ForwardRefRenderFunction = (props, r
onTypingComplete,
header,
footer,
+ editable = {},
...otherHtmlProps
} = props;
@@ -60,6 +63,28 @@ const Bubble: React.ForwardRefRenderFunction = (props, r
// ===================== Component Config =========================
const contextConfig = useXComponentConfig('bubble');
+ // =========================== Editable ===========================
+ const [enableEdit, editConfig] = useMergedConfig(editable);
+ const [isEditing, setIsEditing] = React.useState(editConfig?.editing || false);
+
+ React.useEffect(() => {
+ setIsEditing(editConfig?.editing || false);
+ }, [editConfig?.editing]);
+
+ const onEditChange = (value: string) => {
+ editConfig?.onChange?.(value);
+ };
+
+ const onEditCancel = () => {
+ editConfig?.onCancel?.();
+ setIsEditing(false);
+ };
+
+ const onEditEnd = (value: string) => {
+ editConfig?.onEnd?.(value);
+ setIsEditing(false);
+ };
+
// ============================ Typing ============================
const [typingEnabled, typingStep, typingInterval] = useTypingConfig(typing);
@@ -119,23 +144,43 @@ const Bubble: React.ForwardRefRenderFunction = (props, r
contentNode = mergedContent as React.ReactNode;
}
- let fullContent: React.ReactNode = (
-
- {contentNode}
-
- );
+ let fullContent: React.ReactNode =
+ enableEdit && isEditing ? (
+
+ ) : (
+
+ {contentNode}
+
+ );
if (header || footer) {
fullContent = (
diff --git a/components/bubble/Editor.tsx b/components/bubble/Editor.tsx
new file mode 100644
index 00000000..39a000f8
--- /dev/null
+++ b/components/bubble/Editor.tsx
@@ -0,0 +1,120 @@
+import classNames from 'classnames';
+import * as React from 'react';
+
+import { Button, Flex, Input } from 'antd';
+import { TextAreaRef } from 'antd/lib/input/TextArea';
+import { EditConfig } from './interface';
+import useStyle from './style';
+
+const { TextArea } = Input;
+
+interface EditableProps {
+ prefixCls: string;
+ value: string;
+ onChange?: (value: string) => void;
+ onCancel?: () => void;
+ onEnd?: (value: string) => void;
+ editorClassName?: string;
+ editorStyle?: React.CSSProperties;
+ editorTextAreaConfig?: EditConfig['editorTextAreaConfig'];
+ editorButtonConfig?: EditConfig['editorButtonConfig'];
+}
+
+const Editor: React.FC = (props) => {
+ const {
+ prefixCls,
+ editorClassName: className,
+ editorStyle,
+ value,
+ onChange,
+ onCancel,
+ onEnd,
+ editorTextAreaConfig,
+ editorButtonConfig,
+ } = props;
+ const textAreaRef = React.useRef(null);
+
+ const [current, setCurrent] = React.useState(value);
+
+ React.useEffect(() => {
+ setCurrent(value);
+ }, [value]);
+
+ React.useEffect(() => {
+ if (textAreaRef.current?.resizableTextArea) {
+ const { textArea } = textAreaRef.current.resizableTextArea;
+ textArea.focus();
+ const { length } = textArea.value;
+ textArea.setSelectionRange(length, length);
+ }
+ }, []);
+
+ const onTextAreaChange: React.ChangeEventHandler = ({ target }) => {
+ setCurrent(target.value.replace(/[\n\r]/g, ''));
+ onChange?.(target.value.replace(/[\n\r]/g, ''));
+ };
+
+ const confirmEnd = () => {
+ onEnd?.(current.trim());
+ };
+
+ const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
+
+ const editorClassName = classNames(
+ prefixCls,
+ `${prefixCls}-editor`,
+ className,
+ hashId,
+ cssVarCls,
+ );
+
+ const CancelButton = () =>
+ editorButtonConfig ? (
+ editorButtonConfig
+ .filter((config) => config.type === 'cancel')
+ .map((config, index) => (
+
+ ))
+ ) : (
+
+ );
+ const SaveButton = () =>
+ editorButtonConfig ? (
+ editorButtonConfig
+ .filter((config) => config.type === 'save')
+ .map((config, index) => (
+
+ ))
+ ) : (
+
+ );
+
+ return wrapCSSVar(
+
+
+
+
+
+
+
+
+
,
+ );
+};
+
+export default Editor;
diff --git a/components/bubble/demo/editable.md b/components/bubble/demo/editable.md
new file mode 100644
index 00000000..8e1460c9
--- /dev/null
+++ b/components/bubble/demo/editable.md
@@ -0,0 +1,7 @@
+## zh-CN
+
+通过设置 `editable` 属性,开启对 `content` 的编辑效果。
+
+## en-US
+
+Enable the editing effect of `content` by setting the `editable` property.
diff --git a/components/bubble/demo/editable.tsx b/components/bubble/demo/editable.tsx
new file mode 100644
index 00000000..e5659a8e
--- /dev/null
+++ b/components/bubble/demo/editable.tsx
@@ -0,0 +1,128 @@
+import {
+ DeleteOutlined,
+ EditOutlined,
+ LeftOutlined,
+ RightOutlined,
+ UserOutlined,
+} from '@ant-design/icons';
+import { Bubble } from '@ant-design/x';
+import { Button, Flex } from 'antd';
+import React from 'react';
+import { EditConfig } from '../interface';
+
+const fooAvatar: React.CSSProperties = {
+ color: '#f56a00',
+ backgroundColor: '#fde3cf',
+};
+
+const barAvatar: React.CSSProperties = {
+ color: '#fff',
+ backgroundColor: '#87d068',
+};
+
+const hideAvatar: React.CSSProperties = {
+ visibility: 'hidden',
+};
+
+const App = () => {
+ const [editing, setEditing] = React.useState(false);
+ const [currentIndex, setCurrentIndex] = React.useState(0);
+ const [editHistory, setEditHistory] = React.useState(['Good morning, how are you?']); // 编辑历史记录
+
+ const triggerEdit = () => {
+ setEditing((prev) => !prev);
+ };
+
+ const handleLeftClick = () => {
+ if (currentIndex > 0) {
+ setCurrentIndex((prev) => prev - 1);
+ }
+ };
+
+ const handleRightClick = () => {
+ if (currentIndex < editHistory.length - 1) {
+ setCurrentIndex((prev) => prev + 1);
+ }
+ };
+
+ const cancelEdit = () => {
+ setEditing(false);
+ };
+
+ const endEdit = (c: string) => {
+ setEditing(false);
+ setEditHistory((prev) => [...prev, c]);
+ setCurrentIndex(editHistory.length);
+ };
+
+ const editConfig: EditConfig = {
+ editing,
+ onCancel: cancelEdit,
+ onEnd: endEdit,
+ editorTextAreaConfig: { autoSize: { minRows: 2, maxRows: 4 } },
+ editorButtonConfig: [
+ {
+ type: 'cancel',
+ text: 'Cancel',
+ option: { size: 'small' },
+ },
+ {
+ type: 'save',
+ text: 'Save',
+ option: { size: 'small', type: 'primary' },
+ },
+ ],
+ };
+
+ return (
+
+ , style: barAvatar }}
+ header={
+ editHistory.length > 1 &&
+ !editing && (
+
+ }
+ onClick={handleLeftClick}
+ disabled={currentIndex === 0}
+ />
+ {`${currentIndex + 1} / ${editHistory.length}`}
+ }
+ onClick={handleRightClick}
+ disabled={currentIndex === editHistory.length - 1}
+ />
+
+ )
+ }
+ footer={
+
+ } />
+ } onClick={triggerEdit} />
+
+ }
+ />
+ , style: fooAvatar }}
+ />
+
+
+ );
+};
+
+export default App;
diff --git a/components/bubble/hooks/useMergedConfig.ts b/components/bubble/hooks/useMergedConfig.ts
new file mode 100644
index 00000000..bd16c97e
--- /dev/null
+++ b/components/bubble/hooks/useMergedConfig.ts
@@ -0,0 +1,18 @@
+import * as React from 'react';
+
+export default function useMergedConfig(
+ propConfig: any,
+ templateConfig?: Target,
+): readonly [boolean, Target] {
+ return React.useMemo(() => {
+ const support = !!propConfig;
+
+ return [
+ support,
+ {
+ ...templateConfig,
+ ...(support && typeof propConfig === 'object' ? propConfig : null),
+ },
+ ] as const;
+ }, [propConfig]);
+}
diff --git a/components/bubble/index.en-US.md b/components/bubble/index.en-US.md
index 48705db7..eaef5d13 100644
--- a/components/bubble/index.en-US.md
+++ b/components/bubble/index.en-US.md
@@ -22,6 +22,7 @@ Often used when chatting.
Placement and avatar
Header and footer
Loading
+Editing effect
Typing effect
Content render
Variant
@@ -62,6 +63,30 @@ Common props ref:[Common props](/docs/react/common-props)
| items | Bubble items list | (BubbleProps & { key?: string \| number, role?: string })[] | - | |
| roles | Set the default properties of the bubble. The `role` in `items` will be automatically matched. | Record \| (bubble) => BubbleProps | - | |
+### editable
+
+```tsx
+interface EditConfig {
+ editing?: boolean;
+ onChange?: (content: string) => void;
+ onCancel?: () => void;
+ onEnd?: (content: string) => void;
+ editorClassNames?: string;
+ editorStyles?: CSSProperties;
+ editorTextAreaConfig?: TextAreaProps;
+ editorButtonConfig?: { type: 'save' | 'cancel'; text?: string; option?: ButtonProps }[];
+}
+```
+
+| Property | Description | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| editing | Wether to be editable | boolean | false | |
+| onChange | Called when input at textarea | (content?:string) => void | - | |
+| onCancel | Called when exiting the editable state | () => void | - | |
+| onEnd | Called when saving or ending the editable state | (content?:string) => void | - | |
+| editorTextAreaConfig | Configure settings related to textarea in the editor | TextAreaProps | variant="borderless" autoSize={{ minRows: 2, maxRows: 3 }} | |
+| editorButtonConfig | Configure settings related to button in the editor | { type: 'save' \| 'cancel'; text?: string; option?: ButtonProps }[] | [{ type: 'save', text: 'Save', option: { size: 'small', type: 'primary' } }, { type: 'cancel', text: 'Cancel', option: { size: 'small' } }] | |
+
## Semantic DOM
diff --git a/components/bubble/index.zh-CN.md b/components/bubble/index.zh-CN.md
index 7985340e..2d2405a9 100644
--- a/components/bubble/index.zh-CN.md
+++ b/components/bubble/index.zh-CN.md
@@ -23,6 +23,7 @@ demo:
支持位置和头像
头和尾
加载中
+编辑效果
打字效果
自定义渲染
变体
@@ -63,6 +64,30 @@ demo:
| items | 气泡数据列表 | (BubbleProps & { key?: string \| number, role?: string })[] | - | |
| roles | 设置气泡默认属性,`items` 中的 `role` 会进行自动对应 | Record \| (bubble) => BubbleProps | - | |
+### editable
+
+```tsx
+interface EditConfig {
+ editing?: boolean;
+ onChange?: (content: string) => void;
+ onCancel?: () => void;
+ onEnd?: (content: string) => void;
+ editorClassNames?: string;
+ editorStyles?: CSSProperties;
+ editorTextAreaConfig?: TextAreaProps;
+ editorButtonConfig?: { type: 'save' | 'cancel'; text?: string; option?: ButtonProps }[];
+}
+```
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| editing | 控制是否是编辑中状态 | boolean | false | |
+| onChange | 文本域编辑时触发 | (content?:string) => void | - | |
+| onCancel | 退出编辑状态时触发 | () => void | - | |
+| onEnd | 保存/结束编辑状态时触发 | (content?:string) => void | - | |
+| editorTextAreaConfig | 配置编辑器中文本域的相关设置 | TextAreaProps | variant="borderless" autoSize={{ minRows: 2, maxRows: 3 }} | |
+| editorButtonConfig | 配置编辑器中按钮的相关设置 | { type: 'save' \| 'cancel'; text?: string; option?: ButtonProps }[] | [{ type: 'save', text: 'Save', option: { size: 'small', type: 'primary' } }, { type: 'cancel', text: 'Cancel', option: { size: 'small' } }] | |
+
## Semantic DOM
diff --git a/components/bubble/interface.ts b/components/bubble/interface.ts
index 5b939090..4dff0985 100644
--- a/components/bubble/interface.ts
+++ b/components/bubble/interface.ts
@@ -1,4 +1,6 @@
import type { AvatarProps } from 'antd';
+import { TextAreaProps } from 'antd/es/input';
+import { ButtonProps } from 'antd/lib';
export interface TypingOption {
/**
@@ -11,7 +13,18 @@ export interface TypingOption {
interval?: number;
}
-type SemanticType = 'avatar' | 'content' | 'header' | 'footer';
+type SemanticType = 'avatar' | 'content' | 'header' | 'footer' | 'editor';
+
+type EditorButtonConfig = { type: 'save' | 'cancel'; text?: string; option?: ButtonProps };
+
+export interface EditConfig {
+ editing?: boolean;
+ onChange?: (content: string) => void;
+ onCancel?: VoidFunction;
+ onEnd?: (content: string) => void;
+ editorTextAreaConfig?: TextAreaProps;
+ editorButtonConfig?: EditorButtonConfig[];
+}
export interface BubbleProps extends Omit, 'content'> {
prefixCls?: string;
@@ -30,4 +43,5 @@ export interface BubbleProps extends Omit,
onTypingComplete?: VoidFunction;
header?: React.ReactNode;
footer?: React.ReactNode;
+ editable?: EditConfig;
}
diff --git a/components/bubble/style/index.ts b/components/bubble/style/index.ts
index c45040fb..446b72ac 100644
--- a/components/bubble/style/index.ts
+++ b/components/bubble/style/index.ts
@@ -50,6 +50,9 @@ const genBubbleStyle: GenerateStyle = (token) => {
[`&${componentCls}-end`]: {
justifyContent: 'end',
flexDirection: 'row-reverse',
+ [`& ${componentCls}-content-wrapper`]: {
+ alignItems: 'flex-end',
+ },
},
[`&${componentCls}-rtl`]: {
direction: 'rtl',
@@ -90,15 +93,19 @@ const genBubbleStyle: GenerateStyle = (token) => {
// =========================== Content =============================
[`& ${componentCls}-content-wrapper`]: {
- flex: 'auto',
+ flex: 'auto', //
display: 'flex',
flexDirection: 'column',
- alignItems: 'flex-start',
+ alignItems: 'flex-start', //
+ minWidth: 0,
+ maxWidth: '100%',
},
[`& ${componentCls}-content`]: {
position: 'relative',
boxSizing: 'border-box',
+ minWidth: 0,
+ maxWidth: '100%',
color: colorText,
fontSize: token.fontSize,
@@ -135,6 +142,15 @@ const genBubbleStyle: GenerateStyle = (token) => {
},
},
},
+ // =========================== Editor =============================
+ [`& ${componentCls}-editor`]: {
+ minWidth: '30%',
+ maxWidth: '100%',
+ border: `1px solid ${token.colorPrimary}`,
+ padding: token.paddingSM,
+ borderRadius: token.borderRadius,
+ boxShadow: token.boxShadow,
+ },
},
};
};
diff --git a/package.json b/package.json
index 6ab2b423..54a693bb 100644
--- a/package.json
+++ b/package.json
@@ -137,7 +137,6 @@
"@types/markdown-it": "^14.1.2",
"@types/minimist": "^1.2.5",
"@types/node": "^22.5.5",
- "@types/nprogress": "^0.2.3",
"@types/ora": "^3.2.0",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.5",
@@ -211,7 +210,6 @@
"mockdate": "^3.0.5",
"node-fetch": "^3.3.2",
"node-notifier": "^10.0.1",
- "nprogress": "^0.2.0",
"open": "^10.1.0",
"ora": "^8.1.0",
"pixelmatch": "^6.0.0",
diff --git a/tests/utils.tsx b/tests/utils.tsx
index 3355faf8..91bf146c 100644
--- a/tests/utils.tsx
+++ b/tests/utils.tsx
@@ -1,10 +1,10 @@
-import type { ReactElement } from 'react';
-import React, { createRef, StrictMode } from 'react';
-import type { RenderOptions } from '@testing-library/react';
+import type { RenderOptions, RenderResult } from '@testing-library/react';
import { act, render } from '@testing-library/react';
import MockDate from 'mockdate';
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
+import type { ReactElement } from 'react';
+import React, { createRef, StrictMode } from 'react';
export function assertsExist(item?: T): asserts item is T {
expect(item).not.toBeUndefined();
@@ -29,7 +29,7 @@ export const sleep = async (timeout = 0) => {
});
};
-const customRender = (ui: ReactElement, options?: Omit) =>
+const customRender = (ui: ReactElement, options?: Omit): RenderResult =>
render(ui, { wrapper: StrictMode, ...options });
export function renderHook(func: () => T): { result: React.RefObject } {