diff --git a/.dumi/theme/builtins/Previewer/CodePreviewer.tsx b/.dumi/theme/builtins/Previewer/CodePreviewer.tsx index 8af1d33912..7b4d5519cf 100644 --- a/.dumi/theme/builtins/Previewer/CodePreviewer.tsx +++ b/.dumi/theme/builtins/Previewer/CodePreviewer.tsx @@ -1,11 +1,5 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; -import { - CheckOutlined, - LinkOutlined, - SnippetsOutlined, - ThunderboltOutlined, - UpOutlined, -} from '@ant-design/icons'; +import { LinkOutlined, ThunderboltOutlined, UpOutlined } from '@ant-design/icons'; import type { Project } from '@stackblitz/sdk'; import stackblitzSdk from '@stackblitz/sdk'; import { Alert, Badge, Space, Tooltip } from 'antd'; @@ -13,7 +7,6 @@ import { createStyles, css } from 'antd-style'; import classNames from 'classnames'; import { FormattedMessage, useSiteData } from 'dumi'; import LZString from 'lz-string'; -import CopyToClipboard from 'react-copy-to-clipboard'; import type { AntdPreviewerProps } from '.'; import useLocation from '../../../hooks/useLocation'; @@ -125,8 +118,6 @@ const CodePreviewer: React.FC = (props) => { const riddleIconRef = useRef(null); const codepenIconRef = useRef(null); const [codeExpand, setCodeExpand] = useState(false); - const [copyTooltipOpen, setCopyTooltipOpen] = useState(false); - const [copied, setCopied] = useState(false); const [codeType, setCodeType] = useState('tsx'); const { theme } = useContext(SiteContext); @@ -147,18 +138,6 @@ const CodePreviewer: React.FC = (props) => { track({ type: 'expand', demo }); }; - const handleCodeCopied = (demo: string) => { - setCopied(true); - track({ type: 'copy', demo }); - }; - - const onCopyTooltipOpenChange = (open: boolean) => { - setCopyTooltipOpen(open); - if (open) { - setCopied(false); - } - }; - useEffect(() => { if (asset.id === hash.slice(1)) { anchorRef.current?.click(); @@ -483,17 +462,6 @@ createRoot(document.getElementById('container')).render(); - handleCodeCopied(asset.id)}> - } - > - {React.createElement(copied && copyTooltipOpen ? CheckOutlined : SnippetsOutlined, { - className: 'code-box-code-copy code-box-code-action', - })} - - }> ); if (!style) { return; } - const styleTag = document.createElement('style'); + const styleTag = document.createElement('style') as HTMLStyleElement; styleTag.type = 'text/css'; styleTag.innerHTML = style; - styleTag['data-demo-url'] = demoUrl; + (styleTag as any)['data-demo-url'] = demoUrl; document.head.appendChild(styleTag); return () => { document.head.removeChild(styleTag); @@ -576,7 +544,7 @@ createRoot(document.getElementById('container')).render(); if (version) { return ( - + {codeBox} ); diff --git a/.dumi/theme/builtins/Previewer/DesignPreviewer.tsx b/.dumi/theme/builtins/Previewer/DesignPreviewer.tsx index c64ad7966b..d3b591aeba 100644 --- a/.dumi/theme/builtins/Previewer/DesignPreviewer.tsx +++ b/.dumi/theme/builtins/Previewer/DesignPreviewer.tsx @@ -5,7 +5,7 @@ import { CheckOutlined, SketchOutlined } from '@ant-design/icons'; import { nodeToGroup } from 'html2sketch'; import copy from 'copy-to-clipboard'; import { App } from 'antd'; -import type { AntdPreviewerProps } from '.'; +import type { AntdPreviewerProps } from './Previewer'; const useStyle = createStyles(({ token, css }) => ({ wrapper: css` diff --git a/.dumi/theme/common/CodePreview.tsx b/.dumi/theme/common/CodePreview.tsx index 827630fcc0..b58b4661d0 100644 --- a/.dumi/theme/common/CodePreview.tsx +++ b/.dumi/theme/common/CodePreview.tsx @@ -1,8 +1,43 @@ import React, { useEffect, useMemo } from 'react'; -import { Tabs } from 'antd'; +import { Tabs, Typography, Button } from 'antd'; import toReactElement from 'jsonml-to-react-element'; import JsonML from 'jsonml.js/lib/utils'; import Prism from 'prismjs'; +import { createStyles } from 'antd-style'; + +const useStyle = createStyles(({ token, css }) => { + const { colorIcon, colorBgTextHover, antCls } = token; + + return { + code: css` + position: relative; + `, + + copyButton: css` + color: ${colorIcon}; + position: absolute; + top: 0; + inset-inline-end: 16px; + width: 32px; + text-align: center; + background: ${colorBgTextHover}; + padding: 0; + `, + + copyIcon: css` + ${antCls}-typography-copy { + margin-inline-start: 0; + } + ${antCls}-typography-copy:not(${antCls}-typography-copy-success) { + color: ${colorIcon}; + + &:hover { + color: ${colorIcon}; + } + } + `, + }; +}); const LANGS = { tsx: 'TypeScript', @@ -40,7 +75,7 @@ const CodePreview: React.FC = ({ onCodeTypeChange, }) => { // 避免 Tabs 数量不稳定的闪动问题 - const initialCodes = {}; + const initialCodes = {} as Record<'tsx' | 'jsx' | 'style', string>; if (sourceCode) { initialCodes.tsx = ''; } @@ -51,7 +86,11 @@ const CodePreview: React.FC = ({ initialCodes.style = ''; } const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes); - + const sourceCodes = { + tsx: sourceCode, + jsx: jsxCode, + style: styleCode, + } as Record<'tsx' | 'jsx' | 'style', string>; useEffect(() => { const codes = { tsx: Prism.highlight(sourceCode, Prism.languages.javascript, 'jsx'), @@ -59,7 +98,7 @@ const CodePreview: React.FC = ({ style: Prism.highlight(styleCode, Prism.languages.css, 'css'), }; // 去掉空的代码类型 - Object.keys(codes).forEach((key) => { + Object.keys(codes).forEach((key: keyof typeof codes) => { if (!codes[key]) { delete codes[key]; } @@ -68,12 +107,22 @@ const CodePreview: React.FC = ({ }, [jsxCode, sourceCode, styleCode]); const langList = Object.keys(highlightedCodes); + + const { styles } = useStyle(); + const items = useMemo( () => - langList.map((lang) => ({ + langList.map((lang: keyof typeof LANGS) => ({ label: LANGS[lang], key: lang, - children: toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }]), + children: ( +
+ {toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])} + +
+ ), })), [JSON.stringify(highlightedCodes)], ); @@ -85,7 +134,11 @@ const CodePreview: React.FC = ({ if (langList.length === 1) { return toReactComponent([ 'pre', - { lang: langList[0], highlighted: highlightedCodes[langList[0]], className: 'highlight' }, + { + lang: langList[0], + highlighted: highlightedCodes[langList[0] as keyof typeof LANGS], + className: 'highlight', + }, ]); } diff --git a/components/button/LoadingIcon.tsx b/components/button/LoadingIcon.tsx index 8ad0f3d05f..74918bb19c 100644 --- a/components/button/LoadingIcon.tsx +++ b/components/button/LoadingIcon.tsx @@ -56,6 +56,7 @@ const LoadingIcon: React.FC = (props) => { visible={visible} // We do not really use this motionName motionName={`${prefixCls}-loading-icon-motion`} + motionLeave={visible} removeOnLeave onAppearStart={getCollapsedWidth} onAppearActive={getRealWidth} diff --git a/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap index c440ad0a91..ba74f2f586 100644 --- a/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -158,6 +158,20 @@ exports[`renders components/button/demo/chinese-chars-loading.tsx extend context class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small" style="flex-wrap: wrap;" > +
+ +
diff --git a/components/button/__tests__/__snapshots__/demo.test.ts.snap b/components/button/__tests__/__snapshots__/demo.test.ts.snap index 6c911d83aa..468663d464 100644 --- a/components/button/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/button/__tests__/__snapshots__/demo.test.ts.snap @@ -154,6 +154,20 @@ exports[`renders components/button/demo/chinese-chars-loading.tsx correctly 1`] class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small" style="flex-wrap:wrap" > +
+ +
diff --git a/components/button/__tests__/index.test.tsx b/components/button/__tests__/index.test.tsx index 9e3e7cae30..bde35b43d6 100644 --- a/components/button/__tests__/index.test.tsx +++ b/components/button/__tests__/index.test.tsx @@ -1,6 +1,6 @@ import { SearchOutlined } from '@ant-design/icons'; import { resetWarned } from 'rc-util/lib/warning'; -import React, { useState } from 'react'; +import React, { Suspense, useRef, useState } from 'react'; import { act } from 'react-dom/test-utils'; import Button from '..'; import mountTest from '../../../tests/shared/mountTest'; @@ -388,4 +388,44 @@ describe('Button', () => { expect(refHtml).toBeTruthy(); expect(btnAttr).toBeTruthy(); }); + + it('should not display loading when not set', () => { + function Suspender({ freeze }: { freeze: boolean }) { + const promiseCache = useRef<{ + promise?: Promise; + resolve?: (value: void | PromiseLike) => void; + }>({}).current; + if (freeze && !promiseCache.promise) { + promiseCache.promise = new Promise((resolve) => { + promiseCache.resolve = resolve; + }); + throw promiseCache.promise; + } else if (freeze) { + throw promiseCache.promise; + } else if (promiseCache.promise) { + promiseCache.resolve?.(); + promiseCache.promise = undefined; + } + return ; + } + const MyCom: React.FC = () => { + const [freeze, setFreeze] = useState(false); + return ( +
+ + frozen}> + + +
+ ); + }; + const { container } = render(); + + fireEvent.click(container.querySelector('.change-btn')!); + expect(container.querySelector('.foo')).toHaveTextContent('frozen'); + fireEvent.click(container.querySelector('.change-btn')!); + expect(container.querySelectorAll('.ant-btn-loading-icon').length).toBe(0); + }); }); diff --git a/components/button/demo/chinese-chars-loading.tsx b/components/button/demo/chinese-chars-loading.tsx index 89f57c1815..791772f010 100644 --- a/components/button/demo/chinese-chars-loading.tsx +++ b/components/button/demo/chinese-chars-loading.tsx @@ -9,6 +9,11 @@ const Text3 = () => 'Submit'; const App: React.FC = () => ( +
@@ -307,7 +307,7 @@ exports[`Tour controlled current 1`] = `
@@ -696,7 +696,7 @@ exports[`Tour step support Primary 1`] = `
diff --git a/components/tour/__tests__/index.test.tsx b/components/tour/__tests__/index.test.tsx index 3c1444bd7f..fede3f9fa0 100644 --- a/components/tour/__tests__/index.test.tsx +++ b/components/tour/__tests__/index.test.tsx @@ -529,4 +529,38 @@ describe('Tour', () => { resetIndex(); }); + + it('first step should be primary', () => { + const App: React.FC = () => { + const coverBtnRef = useRef(null); + return ( + <> + + + coverBtnRef.current!, + type: 'primary', + className: 'should-be-primary', + }, + { + title: '', + target: () => coverBtnRef.current!, + }, + ]} + /> + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', { name: 'target' })); + expect(document.querySelector('.should-be-primary')).toBeTruthy(); + expect(document.querySelector('.should-be-primary')).toHaveClass('ant-tour-primary'); + }); }); diff --git a/components/tour/__tests__/useMergedType.test.ts b/components/tour/__tests__/useMergedType.test.ts deleted file mode 100644 index 147b2a062d..0000000000 --- a/components/tour/__tests__/useMergedType.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { act, renderHook } from '../../../tests/utils'; -import useMergedType from '../useMergedType'; - -describe('useMergedType', () => { - it('returns the merged type', () => { - const { result } = renderHook(() => - useMergedType({ - defaultType: 'default', - steps: [{ type: 'primary', title: 'Step 1' }], - current: 0, - }), - ); - expect(result.current?.currentMergedType).toBe('primary'); - }); - - it('returns the default type', () => { - const { result } = renderHook(() => - useMergedType({ - defaultType: 'default', - steps: [], - current: 0, - }), - ); - expect(result.current?.currentMergedType).toBe('default'); - }); - - it('returns the default type when index is invalid', () => { - const { result } = renderHook(() => - useMergedType({ - defaultType: 'default', - steps: [], - current: 0, - }), - ); - expect(result.current?.currentMergedType).toBe('default'); - }); - - it('returns the default type when list is null', () => { - const { result } = renderHook(() => - useMergedType({ - defaultType: 'default', - }), - ); - expect(result.current?.currentMergedType).toBe('default'); - }); - - it('returns type of new step after onChange from rc-tour', () => { - const { result } = renderHook(() => - useMergedType({ - defaultType: 'default', - steps: [{ title: 'Step 1' }, { type: 'primary', title: 'Step 1' }], - }), - ); - act(() => { - result.current?.updateInnerCurrent?.(1); - }); - - expect(result.current?.currentMergedType).toBe('primary'); - }); -}); diff --git a/components/tour/index.tsx b/components/tour/index.tsx index 9bcc9dcaba..ab3207d823 100644 --- a/components/tour/index.tsx +++ b/components/tour/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import RCTour from '@rc-component/tour'; import classNames from 'classnames'; @@ -10,15 +10,12 @@ import type { TourProps, TourStepProps } from './interface'; import TourPanel from './panelRender'; import PurePanel from './PurePanel'; import useStyle from './style'; -import useMergedType from './useMergedType'; const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel } = ( props, ) => { const { prefixCls: customizePrefixCls, - current, - defaultCurrent, type, rootClassName, indicatorsRender, @@ -30,12 +27,16 @@ const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: type const [wrapSSR, hashId] = useStyle(prefixCls); const [, token] = useToken(); - const { currentMergedType, updateInnerCurrent } = useMergedType({ - defaultType: type, - steps, - current, - defaultCurrent, - }); + const mergedSteps = useMemo( + () => + steps?.map((step) => ({ + ...step, + className: classNames(step.className, { + [`${prefixCls}-primary`]: (step.type ?? type) === 'primary', + }), + })), + [steps, type], + ); const builtinPlacements = getPlacements({ arrowPointAtCenter: true, @@ -47,7 +48,6 @@ const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: type const customClassName = classNames( { - [`${prefixCls}-primary`]: currentMergedType === 'primary', [`${prefixCls}-rtl`]: direction === 'rtl', }, hashId, @@ -63,23 +63,15 @@ const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: type /> ); - const onStepChange = (stepCurrent: number) => { - updateInnerCurrent(stepCurrent); - props.onChange?.(stepCurrent); - }; - return wrapSSR( , ); }; diff --git a/components/tour/panelRender.tsx b/components/tour/panelRender.tsx index 1d56851738..dc30437f0d 100644 --- a/components/tour/panelRender.tsx +++ b/components/tour/panelRender.tsx @@ -43,7 +43,6 @@ const TourPanel: React.FC = ({ nextButtonProps, prevButtonProps, type: stepType, - className, closeIcon: stepCloseIcon, } = stepProps; @@ -120,7 +119,7 @@ const TourPanel: React.FC = ({ const [contextLocale] = useLocale('Tour', defaultLocale.Tour); return ( -
+
{closable && mergedDisplayCloseIcon} {coverNode} diff --git a/components/tour/useMergedType.ts b/components/tour/useMergedType.ts deleted file mode 100644 index b5b90d02db..0000000000 --- a/components/tour/useMergedType.ts +++ /dev/null @@ -1,31 +0,0 @@ -import useMergedState from 'rc-util/lib/hooks/useMergedState'; -import { useLayoutEffect } from 'react'; -import type { TourProps } from './interface'; - -interface Props { - defaultType?: string; - steps?: TourProps['steps']; - current?: number; - defaultCurrent?: number; -} - -/** - * returns the merged type of a step or the default type. - */ -const useMergedType = ({ defaultType, steps = [], current, defaultCurrent }: Props) => { - const [innerCurrent, updateInnerCurrent] = useMergedState(defaultCurrent, { - value: current, - }); - - useLayoutEffect(() => { - if (current === undefined) return; - updateInnerCurrent(current); - }, [current]); - - const innerType = typeof innerCurrent === 'number' ? steps[innerCurrent]?.type : defaultType; - const currentMergedType = innerType ?? defaultType; - - return { currentMergedType, updateInnerCurrent }; -}; - -export default useMergedType; diff --git a/docs/blog/hydrate-cssinjs.zh-CN.md b/docs/blog/hydrate-cssinjs.zh-CN.md index ffd29ca620..7b9a8da12a 100644 --- a/docs/blog/hydrate-cssinjs.zh-CN.md +++ b/docs/blog/hydrate-cssinjs.zh-CN.md @@ -38,7 +38,7 @@ author: zombieJ ![Component CSS-in-JS](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*yZMNSYVtxnAAAAAAAAAAAAAADrJ8AQ/original) -也因此,我们发现可以复用这套机制,实现在在客户端侧感知组件样式是否已经注入过。 +也因此,我们发现可以复用这套机制,实现在客户端侧感知组件样式是否已经注入过。 ## SSR HashMap diff --git a/docs/react/faq.zh-CN.md b/docs/react/faq.zh-CN.md index 18b2f41577..bb201668cb 100644 --- a/docs/react/faq.zh-CN.md +++ b/docs/react/faq.zh-CN.md @@ -191,7 +191,7 @@ npm ls dayjs 你可以参照 [这篇文章](https://juejin.im/post/5cf65c366fb9a07eca6968f9) 或者 [这篇文章](https://www.cnblogs.com/zyl-Tara/p/10197177.html) 里的做法,利用 `mode` 和 `onPanelChange` 等方法去封装一个 `YearPicker` 等组件。 -另外我们已经在在 [antd@4.0](https://github.com/ant-design/ant-design/issues/16911) 中直接[添加了更多相关日期组件](https://github.com/ant-design/ant-design/issues/4524#issuecomment-480576884)来支持这些需求,现在不再需要使用 `mode="year|month"`,而是直接可以用 `YearPicker` `MonthPicker`,并且 `disabledDate` 也可以正确作用于这些 Picker。 +另外我们已经在 [antd@4.0](https://github.com/ant-design/ant-design/issues/16911) 中直接[添加了更多相关日期组件](https://github.com/ant-design/ant-design/issues/4524#issuecomment-480576884)来支持这些需求,现在不再需要使用 `mode="year|month"`,而是直接可以用 `YearPicker` `MonthPicker`,并且 `disabledDate` 也可以正确作用于这些 Picker。 ## ConfigProvider 设置 `prefixCls` 后,message/notification/Modal.confirm 生成的节点样式丢失了? diff --git a/docs/spec/research-navigation.zh-CN.md b/docs/spec/research-navigation.zh-CN.md index fb9482a38c..d5b5ac5949 100644 --- a/docs/spec/research-navigation.zh-CN.md +++ b/docs/spec/research-navigation.zh-CN.md @@ -201,7 +201,7 @@ title: 导航 ### 页内导航 -信息架构中较低层级的内容导航可以使用页内导航,如果页面需要分享给他人,需在在 url 添加定位标记。 +信息架构中较低层级的内容导航可以使用页内导航,如果页面需要分享给他人,需在 url 添加定位标记。 #### 页头 diff --git a/package.json b/package.json index adf0bf9b7c..3f82854b5c 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "rc-slider": "~10.3.0", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.34.0", + "rc-table": "~7.34.4", "rc-tabs": "~12.12.1", "rc-textarea": "~1.4.0", "rc-tooltip": "~6.1.0", @@ -161,7 +161,7 @@ "devDependencies": { "@ant-design/compatible": "^5.1.2", "@ant-design/happy-work-theme": "^1.0.0", - "@ant-design/tools": "^17.3.1", + "@ant-design/tools": "^17.3.2", "@antv/g6": "^4.8.13", "@argos-ci/core": "^0.12.0", "@babel/eslint-plugin": "^7.19.1", @@ -217,7 +217,7 @@ "cross-fetch": "^4.0.0", "crypto": "^1.0.1", "dekko": "^0.2.1", - "dumi": "^2.2.10", + "dumi": "^2.3.0-alpha.4", "dumi-plugin-color-chunk": "^1.0.2", "duplicate-package-checker-webpack-plugin": "^3.0.0", "esbuild-loader": "^4.0.0", @@ -237,7 +237,7 @@ "fetch-jsonp": "^1.1.3", "fs-extra": "^11.0.0", "gh-pages": "^6.0.0", - "glob": "10.3.6", + "glob": "^10.3.7", "html2sketch": "^1.0.0", "http-server": "^14.0.0", "husky": "^8.0.1", @@ -330,5 +330,10 @@ "lint-staged": { "*.{ts,tsx,js,jsx}": "biome format --write", "*.{json,less,md}": "prettier --ignore-unknown --write" + }, + "overrides": { + "dumi-plugin-color-chunk": { + "dumi": "^2.3.0-alpha.4" + } } } diff --git a/tsconfig.json b/tsconfig.json index d04de09c99..bfa157a5c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ }, "strictNullChecks": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "Bundler", "esModuleInterop": true, "experimentalDecorators": true, "jsx": "react", @@ -22,7 +22,8 @@ "target": "es6", "lib": ["dom", "es2017"], "skipLibCheck": true, - "stripInternal": true + "stripInternal": true, + "resolvePackageJsonExports": true }, "include": [".dumirc.ts", "**/*"], "exclude": ["node_modules", "lib", "es"]