Browse Source

Merge branch 'master' into next-merge-master

pull/38096/head
MadCcc 2 years ago
parent
commit
7a38804f95
  1. 38
      .circleci/config.yml
  2. 63
      .github/workflows/ui-upload.yml
  3. 93
      .github/workflows/ui.yml
  4. 19
      CHANGELOG.en-US.md
  5. 19
      CHANGELOG.zh-CN.md
  6. 6
      Dockerfile.ui-test
  7. 2
      components/_util/reactNode.ts
  8. 41
      components/anchor/Anchor.tsx
  9. 100
      components/anchor/AnchorLink.tsx
  10. 43
      components/anchor/__tests__/Anchor.test.tsx
  11. 2
      components/anchor/context.ts
  12. 27
      components/form/__tests__/index.test.tsx
  13. 3
      components/locale/fr_FR.tsx
  14. 2
      components/table/index.en-US.md
  15. 2
      components/table/index.zh-CN.md
  16. 2
      components/tree/index.en-US.md
  17. 28
      components/typography/Base/Ellipsis.tsx
  18. 27
      components/typography/Base/index.tsx
  19. 5
      components/typography/Editable.tsx
  20. 16
      components/typography/Link.tsx
  21. 12
      components/typography/Paragraph.tsx
  22. 5
      components/typography/Text.tsx
  23. 23
      components/typography/Title.tsx
  24. 97
      components/typography/Typography.tsx
  25. 4
      components/typography/__tests__/__snapshots__/index.test.tsx.snap
  26. 17
      components/typography/__tests__/ellipsis.test.tsx
  27. 17
      docker-compose.yml
  28. 9
      package.json
  29. 2
      scripts/argos-upload.js
  30. 1
      scripts/print-changelog.js
  31. 37
      site/theme/template/Content/Demo/index.jsx

38
.circleci/config.yml

@ -0,0 +1,38 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1
# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
jobs:
test-argos-ci:
docker:
- image: circleci/node:16-browsers
steps:
- checkout
- run:
name: Install node_modules
command: npm i
- run:
name: Install argos cli
command: npm i fast-glob lodash @argos-ci/core
- run:
name: Install puppeteer
command: node node_modules/puppeteer/install.js
- run:
name: Build dist file
command: npm run dist
- run:
name: Run image screenshot tests
command: npm run test-image
- run:
name: Upload screenshots to Argos CI
command: npm run argos
# Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:
test-argos-ci-workflow:
jobs:
- test-argos-ci

63
.github/workflows/ui-upload.yml

@ -1,63 +0,0 @@
# Upload 📷 UI snapshots to argos server, help visual regression testing.
name: 📷 UI Upload
on:
workflow_run:
workflows: ["📷 UI"]
types:
- completed
permissions:
contents: read
jobs:
upload-ui:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
pull-requests: read # for dawidd6/action-download-artifact to query commit hash
name: deploy preview
runs-on: ubuntu-latest
if: >
github.repository == 'ant-design/ant-design' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: checkout
uses: actions/checkout@v3
- name: Download commit artifact
uses: dawidd6/action-download-artifact@v2.23.0
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: commit
- name: Save commit id
id: commit
run: echo "::set-output name=id::$(<commit.txt)"
- name: Download branch artifact
uses: dawidd6/action-download-artifact@v2.23.0
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: branch
- name: Save branch id
id: branch
run: echo "::set-output name=id::$(<branch.txt)"
- name: Download snapshots artifact
uses: dawidd6/action-download-artifact@v2.23.0
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
workflow_conclusion: success
name: snapshots
path: imageSnapshots
- name: Install
run: npm i fast-glob lodash @argos-ci/core
- name: Upload on Argos
id: deploy
run: npm run argos
env:
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_PARALLEL_NONCE: ${{ github.run_id }}

93
.github/workflows/ui.yml

@ -1,93 +0,0 @@
name: 📷 UI
on:
pull_request:
types: [opened, synchronize, reopened]
push:
# Cancel prev CI if new commit come
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
imagesnapshot:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: cache package-lock.json
uses: actions/cache@v3
with:
path: package-temp-dir
key: lock-${{ github.sha }}
- uses: actions/setup-node@v3
with:
node-version: '16'
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |
if [ ! -d "package-temp-dir" ]; then
mkdir package-temp-dir
fi
cp package-lock.json package-temp-dir
- name: cache node_modules
id: node_modules_cache_id
uses: actions/cache@v3
with:
path: node_modules
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
- name: install
if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
run: npm ci
- name: test
run: npm run test-image
- name: upload snapshots artifact
uses: actions/upload-artifact@v3
with:
name: snapshots
path: imageSnapshots/
retention-days: 3
- name: Save commit
if: github.event_name == 'pull_request' && github.base_ref == 'master'
run: echo ${{ github.event.pull_request.head.sha }} > ./commit.txt
- name: Save commit
if: github.event_name == 'push'
run: echo ${{ github.sha }} > ./commit.txt
- name: Upload commit
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: commit
path: ./commit.txt
- name: Save branch
if: github.event_name == 'pull_request' && github.base_ref == 'master'
run: echo pull/${{ github.event.pull_request.number }}/merge > ./branch.txt
- name: Save branch
if: github.event_name == 'push'
run: echo ${GITHUB_REF##*/} > ./branch.txt
- name: Upload branch
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: branch
path: ./branch.txt

19
CHANGELOG.en-US.md

@ -15,6 +15,25 @@ timeline: true
---
## 4.23.6
`2022-10-17`
- Table
- 🐞 Fix Table with sticky header shadow style issue. [#38023](https://github.com/ant-design/ant-design/pull/38023) [@liuycy](https://github.com/liuycy)
- 🐞 Fix Table with `ellipsis` missing `title` attribute. [416c61f](https://github.com/ant-design/ant-design/commit/416c61f)
- 🐞 Fix Breadcrumb not support number `0`. [#38006](https://github.com/ant-design/ant-design/pull/38006) [@li-jia-nan](https://github.com/li-jia-nan)
- Input
- 🐞 Fix Input.TextArea render extra input node when enable `autoSize`. [#38050](https://github.com/ant-design/ant-design/pull/38050)
- 🐞 Fix Input.Password that should not have value prop on input after click toggle icon. [#37900](https://github.com/ant-design/ant-design/pull/37900) [@linxianxi](https://github.com/linxianxi)
- 💄 Fix border style issues for Input.Search in RTL. [#37980](https://github.com/ant-design/ant-design/pull/37980) [@foryuki](https://github.com/foryuki)
- 🐞 Fix AutoComplete warning for unused `dropdownClassName`. [#37974](https://github.com/ant-design/ant-design/pull/37974) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 Fix Typography with ellipsis that the computed `fontSize` style be calculated as empty string in some case. [#37928](https://github.com/ant-design/ant-design/pull/37928) [@zheeeng](https://github.com/zheeeng)
- 🐞 Fix editable Tabs add button missing in edge case. [#37937](https://github.com/ant-design/ant-design/pull/37937)
- 🐞 Fix RangePicker panel blink in some case. [#439](https://github.com/react-component/picker/pull/439)
- 🛠 Refactor Spin with Function Component. [#37969](https://github.com/ant-design/ant-design/pull/37969) [@li-jia-nan](https://github.com/li-jia-nan)
- 🛠 Refactor Statistic.Countdown with Function Component. [#37938](https://github.com/ant-design/ant-design/pull/37938) [@li-jia-nan](https://github.com/li-jia-nan)
## 4.23.5
`2022-10-10`

19
CHANGELOG.zh-CN.md

@ -15,6 +15,25 @@ timeline: true
---
## 4.23.6
`2022-10-17`
- Table
- 🐞 修复 Table 配置固定表头时的阴影样式问题。[#38023](https://github.com/ant-design/ant-design/pull/38023) [@liuycy](https://github.com/liuycy)
- 🐞 修复 Table 配置省略时丢失 `title` 属性问题。[416c61f](https://github.com/ant-design/ant-design/commit/416c61f)
- 🐞 修复 Breadcrumb 不支持数字 `0` 的问题。[#38006](https://github.com/ant-design/ant-design/pull/38006) [@li-jia-nan](https://github.com/li-jia-nan)
- Input
- 🐞 修复 Input.TextArea 配置 `autoSize` 时会额外渲染 input 节点的问题。[#38050](https://github.com/ant-design/ant-design/pull/38050)
- 🐞 修复 Input.Password 在点击隐藏按钮后 input 上会有 value 属性的问题。[#37900](https://github.com/ant-design/ant-design/pull/37900) [@linxianxi](https://github.com/linxianxi)
- 💄 修复 RTL 下 Input.Search 边框样式问题。[#37980](https://github.com/ant-design/ant-design/pull/37980) [@foryuki](https://github.com/foryuki)
- 🐞 修复 AutoComplete 会报未使用的废弃属性 `dropdownClassName` 的问题。[#37974](https://github.com/ant-design/ant-design/pull/37974) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 修复 Typography 省略算法在计算一些元素 fontSize 时为空字符串的情况[#37928](https://github.com/ant-design/ant-design/pull/37928) [@zheeeng](https://github.com/zheeeng)
- 🐞 Fix Tabs 添加按钮在某些边界情况下无法展示的问题。[#37937](https://github.com/ant-design/ant-design/pull/37937)
- 🐞 修复 RangePicker 在某些情况下面板会闪烁的问题。[#439](https://github.com/react-component/picker/pull/439)
- 🛠 重构 Spin 为 Function Component。[#37969](https://github.com/ant-design/ant-design/pull/37969) [@li-jia-nan](https://github.com/li-jia-nan)
- 🛠 重构 Statistic.Countdown 为 Function Component.[#37938](https://github.com/ant-design/ant-design/pull/37938) [@li-jia-nan](https://github.com/li-jia-nan)
## 4.23.5
`2022-10-10`

6
Dockerfile.ui-test

@ -1,6 +0,0 @@
FROM buildkite/puppeteer:10.0.0
RUN mkdir /app
WORKDIR /app
COPY package.json ./
ENV PATH="${PATH}:/app/node_modules/.bin"
COPY . .

2
components/_util/reactNode.ts

@ -3,7 +3,7 @@ import * as React from 'react';
export const { isValidElement } = React;
export function isFragment(child: React.ReactElement): boolean {
return child && child.type === React.Fragment;
return child && isValidElement(child) && child.type === React.Fragment;
}
type AnyObject = Record<PropertyKey, any>;

41
components/anchor/Anchor.tsx

@ -37,10 +37,10 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
const sharpMatcherRegx = /#([\S ]+)$/;
type Section = {
interface Section {
link: string;
top: number;
};
}
export interface AnchorProps {
prefixCls?: string;
@ -92,11 +92,6 @@ export interface AntAnchor {
}
class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigConsumerProps> {
static defaultProps = {
affix: true,
showInkInFixed: false,
};
static contextType = ConfigContext;
state = {
@ -114,20 +109,20 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
private links: string[] = [];
private scrollEvent: any;
private scrollEvent: ReturnType<typeof addEventListener>;
private animating: boolean;
private prefixCls?: string;
// Context
registerLink = (link: string) => {
registerLink: AntAnchor['registerLink'] = link => {
if (!this.links.includes(link)) {
this.links.push(link);
}
};
unregisterLink = (link: string) => {
unregisterLink: AntAnchor['unregisterLink'] = link => {
const index = this.links.indexOf(link);
if (index !== -1) {
this.links.splice(index, 1);
@ -177,7 +172,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
const linkSections: Array<Section> = [];
const container = this.getContainer();
this.links.forEach(link => {
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
const sharpLinkMatch = sharpMatcherRegx.exec(link?.toString());
if (!sharpLinkMatch) {
return;
}
@ -185,10 +180,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
if (target) {
const top = getOffsetTop(target, container);
if (top < offsetTop + bounds) {
linkSections.push({
link,
top,
});
linkSections.push({ link, top });
}
}
});
@ -262,10 +254,9 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
updateInk = () => {
const { prefixCls, wrapperRef } = this;
const anchorNode = wrapperRef.current;
const linkNode = anchorNode?.getElementsByClassName(`${prefixCls}-link-title-active`)[0];
const linkNode = anchorNode?.querySelector<HTMLElement>(`.${prefixCls}-link-title-active`);
if (linkNode) {
this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
this.inkNode.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
}
};
@ -286,8 +277,8 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
className = '',
style,
offsetTop,
affix,
showInkInFixed,
affix = true,
showInkInFixed = false,
children,
onClick,
rootClassName,
@ -316,7 +307,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
[`${prefixCls}-fixed`]: !affix && !showInkInFixed,
});
const wrapperStyle = {
const wrapperStyle: React.CSSProperties = {
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
...style,
};
@ -336,24 +327,24 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
return (
<AnchorContext.Provider value={contextValue}>
{!affix ? (
anchorContent
) : (
{affix ? (
<Affix offsetTop={offsetTop} target={this.getContainer}>
{anchorContent}
</Affix>
) : (
anchorContent
)}
</AnchorContext.Provider>
);
}
}
// just use in test
export type InternalAnchorClass = Anchor;
const AnchorFC = React.forwardRef<Anchor, AnchorProps>((props, ref) => {
const { prefixCls: customizePrefixCls } = props;
const { getPrefixCls } = React.useContext(ConfigContext);
const anchorPrefixCls = getPrefixCls('anchor', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(anchorPrefixCls);

100
components/anchor/AnchorLink.tsx

@ -14,71 +14,53 @@ export interface AnchorLinkProps {
className?: string;
}
class AnchorLink extends React.Component<AnchorLinkProps, any, AntAnchor> {
static defaultProps = {
href: '#',
};
static contextType = AnchorContext;
context: AntAnchor;
const AnchorLink: React.FC<AnchorLinkProps> = props => {
const { href = '#', title, prefixCls: customizePrefixCls, children, className, target } = props;
componentDidMount() {
this.context.registerLink(this.props.href);
}
const context = React.useContext<AntAnchor | undefined>(AnchorContext);
componentDidUpdate({ href: prevHref }: AnchorLinkProps) {
const { href } = this.props;
if (prevHref !== href) {
this.context.unregisterLink(prevHref);
this.context.registerLink(href);
}
}
const { registerLink, unregisterLink, scrollTo, onClick, activeLink } = context || {};
componentWillUnmount() {
this.context.unregisterLink(this.props.href);
}
React.useEffect(() => {
registerLink?.(href);
return () => {
unregisterLink?.(href);
};
}, [href]);
handleClick = (e: React.MouseEvent<HTMLElement>) => {
const { scrollTo, onClick } = this.context;
const { href, title } = this.props;
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
onClick?.(e, { title, href });
scrollTo(href);
scrollTo?.(href);
};
renderAnchorLink = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, href, title, children, className, target } = this.props;
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
const active = this.context.activeLink === href;
const wrapperClassName = classNames(
`${prefixCls}-link`,
{
[`${prefixCls}-link-active`]: active,
},
className,
);
const titleClassName = classNames(`${prefixCls}-link-title`, {
[`${prefixCls}-link-title-active`]: active,
});
return (
<div className={wrapperClassName}>
<a
className={titleClassName}
href={href}
title={typeof title === 'string' ? title : ''}
target={target}
onClick={this.handleClick}
>
{title}
</a>
{children}
</div>
);
};
render() {
return <ConfigConsumer>{this.renderAnchorLink}</ConfigConsumer>;
}
}
return (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
const active = activeLink === href;
const wrapperClassName = classNames(`${prefixCls}-link`, className, {
[`${prefixCls}-link-active`]: active,
});
const titleClassName = classNames(`${prefixCls}-link-title`, {
[`${prefixCls}-link-title-active`]: active,
});
return (
<div className={wrapperClassName}>
<a
className={titleClassName}
href={href}
title={typeof title === 'string' ? title : ''}
target={target}
onClick={handleClick}
>
{title}
</a>
{children}
</div>
);
}}
</ConfigConsumer>
);
};
export default AnchorLink;

43
components/anchor/__tests__/Anchor.test.tsx

@ -41,11 +41,17 @@ describe('Anchor Render', () => {
getClientRectsMock.mockReturnValue({ length: 1 } as DOMRectList);
});
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.clearAllTimers();
jest.useRealTimers();
});
afterAll(() => {
jest.clearAllTimers();
jest.useRealTimers();
getBoundingClientRectMock.mockRestore();
getClientRectsMock.mockRestore();
@ -69,7 +75,7 @@ describe('Anchor Render', () => {
expect(anchorInstance!.state).not.toBe(null);
});
it('Anchor render perfectly for complete href - click', () => {
it('Anchor render perfectly for complete href - click', async () => {
const hash = getHashUrl();
let anchorInstance: InternalAnchorClass;
const { container } = render(
@ -82,6 +88,7 @@ describe('Anchor Render', () => {
</Anchor>,
);
fireEvent.click(container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!);
await waitFakeTimer();
expect(anchorInstance!.state!.activeLink).toBe(`http://www.example.com/#${hash}`);
});
@ -100,13 +107,12 @@ describe('Anchor Render', () => {
</Anchor>,
);
anchorInstance!.handleScrollTo('/#/faq?locale=en#Q1');
expect(anchorInstance!.state.activeLink).toBe('/#/faq?locale=en#Q1');
expect(scrollToSpy).not.toHaveBeenCalled();
await waitFakeTimer();
expect(anchorInstance!.state.activeLink).toBe('/#/faq?locale=en#Q1');
expect(scrollToSpy).toHaveBeenCalled();
});
it('Anchor render perfectly for complete href - scroll', () => {
it('Anchor render perfectly for complete href - scroll', async () => {
const hash = getHashUrl();
const root = createDiv();
render(<div id={hash}>Hello</div>, { container: root });
@ -121,6 +127,7 @@ describe('Anchor Render', () => {
</Anchor>,
);
anchorInstance!.handleScroll();
await waitFakeTimer();
expect(anchorInstance!.state!.activeLink).toBe(`http://www.example.com/#${hash}`);
});
@ -141,10 +148,10 @@ describe('Anchor Render', () => {
);
anchorInstance!.handleScrollTo(`##${hash}`);
await waitFakeTimer();
expect(anchorInstance!.state.activeLink).toBe(`##${hash}`);
const calls = scrollToSpy.mock.calls.length;
await waitFakeTimer();
expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls);
expect(scrollToSpy.mock.calls.length).toBe(calls);
});
it('should remove listener when unmount', async () => {
@ -373,7 +380,7 @@ describe('Anchor Render', () => {
it('Anchor targetOffset prop', async () => {
const hash = getHashUrl();
let dateNowMock;
let dateNowMock: jest.SpyInstance;
function dataNowMockFn() {
let start = 0;
@ -438,7 +445,7 @@ describe('Anchor Render', () => {
// https://github.com/ant-design/ant-design/issues/31941
it('Anchor targetOffset prop when contain spaces', async () => {
const hash = `${getHashUrl()} s p a c e s`;
let dateNowMock;
let dateNowMock: jest.SpyInstance;
function dataNowMockFn() {
let start = 0;
@ -543,13 +550,9 @@ describe('Anchor Render', () => {
});
it('test edge case when getBoundingClientRect return zero size', async () => {
getBoundingClientRectMock.mockReturnValue({
width: 0,
height: 0,
top: 1000,
} as DOMRect);
getBoundingClientRectMock.mockReturnValue({ width: 0, height: 0, top: 1000 } as DOMRect);
const hash = getHashUrl();
let dateNowMock;
let dateNowMock: jest.SpyInstance;
function dataNowMockFn() {
let start = 0;
@ -615,7 +618,7 @@ describe('Anchor Render', () => {
it('test edge case when container is not windows', async () => {
const hash = getHashUrl();
let dateNowMock;
let dateNowMock: jest.SpyInstance;
function dataNowMockFn() {
let start = 0;
@ -758,5 +761,15 @@ describe('Anchor Render', () => {
rerender(<Demo current={hash2} />);
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash2);
});
it('should correct render when href is null', () => {
expect(() => {
render(
<Anchor>
<Link href={null as unknown as string} title="test" />
</Anchor>,
);
fireEvent.scroll(window || document);
}).not.toThrow();
});
});
});

2
components/anchor/context.ts

@ -1,6 +1,6 @@
import * as React from 'react';
import type { AntAnchor } from './Anchor';
const AnchorContext = React.createContext<AntAnchor>(null as any);
const AnchorContext = React.createContext<AntAnchor | undefined>(undefined);
export default AnchorContext;

27
components/form/__tests__/index.test.tsx

@ -619,37 +619,48 @@ describe('Form', () => {
jest.useFakeTimers();
const shouldNotRender = jest.fn();
const StaticInput: React.FC = () => {
const StaticInput: React.FC<React.InputHTMLAttributes<HTMLInputElement>> = ({
id,
value = '',
}) => {
shouldNotRender();
return <Input />;
return <input id={id} value={value} />;
};
const shouldRender = jest.fn();
const DynamicInput: React.FC = () => {
const DynamicInput: React.FC<React.InputHTMLAttributes<HTMLInputElement>> = ({
value = '',
id,
}) => {
shouldRender();
return <Input />;
return <input id={id} value={value} />;
};
const formRef = React.createRef<FormInstance>();
pureRender(
const { container } = pureRender(
<Form ref={formRef}>
<Form.Item>
<StaticInput />
</Form.Item>
<Form.Item name="light">
<DynamicInput />
<DynamicInput id="changed" />
</Form.Item>
</Form>,
);
await waitFakeTimer();
expect(container.querySelector<HTMLInputElement>('#changed')!.value).toEqual('');
expect(shouldNotRender).toHaveBeenCalledTimes(1);
expect(shouldRender).toHaveBeenCalledTimes(1);
formRef.current?.setFieldsValue({ light: 'bamboo' });
formRef.current!.setFieldsValue({ light: 'bamboo' });
await waitFakeTimer(100, 100);
await waitFakeTimer();
expect(formRef.current!.getFieldsValue()).toEqual({ light: 'bamboo' });
expect(container.querySelector<HTMLInputElement>('#changed')!.value).toEqual('bamboo');
expect(shouldNotRender).toHaveBeenCalledTimes(1);
expect(shouldRender).toHaveBeenCalledTimes(2);

3
components/locale/fr_FR.tsx

@ -18,7 +18,8 @@ const localeValues: Locale = {
filterConfirm: 'OK',
filterReset: 'Réinitialiser',
filterEmptyText: 'Aucun filtre',
filterCheckall: 'Sélectionner la page actuelle',
filterCheckall: 'Tout sélectionner',
filterSearchPlaceholder: 'Chercher dans les filtres',
emptyText: 'Aucune donnée',
selectAll: 'Sélectionner la page actuelle',
selectInvert: 'Inverser la sélection de la page actuelle',

2
components/table/index.en-US.md

@ -146,7 +146,7 @@ One of the Table `columns` prop for describing the table's columns, Column has t
| showSorterTooltip | If header show next sorter direction tooltip, override `showSorterTooltip` in table | boolean \| [Tooltip props](/components/tooltip/) | true | |
| sortDirections | Supported sort way, override `sortDirections` in `Table`, could be `ascend`, `descend` | Array | \[`ascend`, `descend`] | |
| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. If you need sort buttons only, set to `true` | function \| boolean | - | |
| sortOrder | Order of sorted values: `'ascend'` `'descend'` `false` | boolean \| string | - | |
| sortOrder | Order of sorted values: `ascend` `descend` `null` | `ascend` \| `descend` \| null | - | |
| title | Title of this column | ReactNode \| ({ sortOrder, sortColumn, filters }) => ReactNode | - | |
| width | Width of this column ([width not working?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241)) | string \| number | - | |
| onCell | Set props on per cell | function(record, rowIndex) | - | |

2
components/table/index.zh-CN.md

@ -147,7 +147,7 @@ const columns = [
| showSorterTooltip | 表头显示下一次排序的 tooltip 提示, 覆盖 table 中 `showSorterTooltip` | boolean \| [Tooltip props](/components/tooltip/#API) | true | |
| sortDirections | 支持的排序方式,覆盖 `Table``sortDirections`, 取值为 `ascend` `descend` | Array | \[`ascend`, `descend`] | |
| sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | function \| boolean | - | |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `ascend` `descend` false | boolean \| string | - | |
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `ascend` `descend` `null` | `ascend` \| `descend` \| null | - | |
| title | 列头显示文字(函数用法 `3.10.0` 后支持) | ReactNode \| ({ sortOrder, sortColumn, filters }) => ReactNode | - | |
| width | 列宽度([指定了也不生效?](https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241)) | string \| number | - | |
| onCell | 设置单元格属性 | function(record, rowIndex) | - | |

2
components/tree/index.en-US.md

@ -111,7 +111,7 @@ File icon realize by using switcherIcon. You can overwrite the style to hide it:
### Why defaultExpandAll not working on ajax data?
`default` prefix prop only works when inited. So `defaultExpandAll` has already executed when ajax load data. You can control `expandedKeys` or render Tree when data loaded to realize expanded all.
`default` prefix prop only works when initializing. So `defaultExpandAll` has already executed when ajax load data. You can control `expandedKeys` or render Tree when data loaded to realize expanded all.
### Virtual scroll limitation

28
components/typography/Base/Ellipsis.tsx

@ -67,6 +67,13 @@ const WALKING = 2;
const DONE_WITH_ELLIPSIS = 3;
const DONE_WITHOUT_ELLIPSIS = 4;
type WalkingState =
| typeof NONE
| typeof PREPARE
| typeof WALKING
| typeof DONE_WITH_ELLIPSIS
| typeof DONE_WITHOUT_ELLIPSIS;
const Ellipsis = ({
enabledMeasure,
children,
@ -76,20 +83,15 @@ const Ellipsis = ({
rows,
onEllipsis,
}: EllipsisProps) => {
const [cutLength, setCutLength] = React.useState<[number, number, number]>([0, 0, 0]);
const [walkingState, setWalkingState] = React.useState<
| typeof NONE
| typeof PREPARE
| typeof WALKING
| typeof DONE_WITH_ELLIPSIS
| typeof DONE_WITHOUT_ELLIPSIS
>(NONE);
const [startLen, midLen, endLen] = cutLength;
const [[startLen, midLen, endLen], setCutLength] = React.useState<
[startLen: number, midLen: number, endLen: number]
>([0, 0, 0]);
const [walkingState, setWalkingState] = React.useState<WalkingState>(NONE);
const [singleRowHeight, setSingleRowHeight] = React.useState(0);
const singleRowRef = React.useRef<HTMLSpanElement>(null);
const midRowRef = React.useRef<HTMLSpanElement>(null);
const singleRowRef = React.useRef<HTMLElement>(null);
const midRowRef = React.useRef<HTMLElement>(null);
const nodeList = React.useMemo(() => toArray(text), [text]);
const totalLen = React.useMemo(() => getNodesLen(nodeList), [nodeList]);
@ -167,7 +169,7 @@ const Ellipsis = ({
const renderMeasure = (
content: React.ReactNode,
ref: React.Ref<HTMLSpanElement>,
ref: React.Ref<HTMLElement>,
style: React.CSSProperties,
) => (
<span
@ -189,7 +191,7 @@ const Ellipsis = ({
</span>
);
const renderMeasureSlice = (len: number, ref: React.Ref<HTMLSpanElement>) => {
const renderMeasureSlice = (len: number, ref: React.Ref<HTMLElement>) => {
const sliceNodeList = sliceNodes(nodeList, len);
return renderMeasure(children(sliceNodeList, true), ref, measureStyle);

27
components/typography/Base/index.tsx

@ -59,7 +59,8 @@ export interface EllipsisConfig {
tooltip?: React.ReactNode | TooltipProps;
}
export interface BlockProps extends TypographyProps {
export interface BlockProps<C extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements>
extends TypographyProps<C> {
title?: string;
editable?: boolean | EditConfig;
copyable?: boolean | CopyConfig;
@ -113,13 +114,9 @@ function toList<T extends any>(val: T | T[]): T[] {
return Array.isArray(val) ? val : [val];
}
interface InternalBlockProps extends BlockProps {
component: string;
}
const ELLIPSIS_STR = '...';
const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
className,
@ -151,7 +148,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
'strong',
'keyboard',
'italic',
]) as any;
]);
// ========================== Editable ==========================
const [enableEdit, editConfig] = useMergedConfig<EditConfig>(editable);
@ -175,7 +172,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
}
}, [editing]);
const onEditClick = (e?: React.MouseEvent<HTMLDivElement>) => {
const onEditClick = (e?: React.MouseEvent<HTMLElement>) => {
e?.preventDefault();
triggerEdit(true);
};
@ -193,7 +190,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
// ========================== Copyable ==========================
const [enableCopy, copyConfig] = useMergedConfig<CopyConfig>(copyable);
const [copied, setCopied] = React.useState(false);
const copyIdRef = React.useRef<NodeJS.Timeout>();
const copyIdRef = React.useRef<number>();
const copyOptions: Pick<CopyConfig, 'format'> = {};
if (copyConfig.format) {
@ -201,7 +198,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
}
const cleanCopyId = () => {
clearTimeout(copyIdRef.current!);
window.clearTimeout(copyIdRef.current!);
};
const onCopyClick = (e?: React.MouseEvent<HTMLDivElement>) => {
@ -214,7 +211,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
// Trigger tips update
cleanCopyId();
copyIdRef.current = setTimeout(() => {
copyIdRef.current = window.setTimeout(() => {
setCopied(false);
}, 3000);
@ -351,7 +348,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
tooltipProps = { title: ellipsisConfig.tooltip };
}
const topAriaLabel = React.useMemo(() => {
const isValid = (val: any) => ['string', 'number'].includes(typeof val);
const isValid = (val: any): val is string | number => ['string', 'number'].includes(typeof val);
if (!enableEllipsis || cssEllipsis) {
return undefined;
@ -490,7 +487,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
return (
<ResizeObserver onResize={onResize} disabled={!mergedEnableEllipsis || cssEllipsis}>
{resizeRef => (
{(resizeRef: React.RefObject<HTMLElement>) => (
<EllipsisTooltip
tooltipProps={tooltipProps}
enabledEllipsis={mergedEnableEllipsis}
@ -515,8 +512,8 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
component={component}
ref={composeRef(resizeRef, typographyRef, ref)}
direction={direction}
onClick={triggerType.includes('text') ? onEditClick : null}
aria-label={topAriaLabel}
onClick={triggerType.includes('text') ? onEditClick : undefined}
aria-label={topAriaLabel?.toString()}
title={title}
{...textProps}
>

5
components/typography/Editable.tsx

@ -5,6 +5,7 @@ import KeyCode from 'rc-util/lib/KeyCode';
import * as React from 'react';
import type { DirectionType } from '../config-provider';
import TextArea from '../input/TextArea';
import type { TextAreaRef } from '../input/TextArea';
import { cloneElement } from '../_util/reactNode';
import useStyle from './style';
@ -39,7 +40,7 @@ const Editable: React.FC<EditableProps> = ({
component,
enterIcon = <EnterOutlined />,
}) => {
const ref = React.useRef<any>();
const ref = React.useRef<TextAreaRef>(null);
const inComposition = React.useRef(false);
const lastKeyCode = React.useRef<number>();
@ -129,7 +130,7 @@ const Editable: React.FC<EditableProps> = ({
return wrapSSR(
<div className={textAreaClassName} style={style}>
<TextArea
ref={ref as any}
ref={ref}
maxLength={maxLength}
value={current}
onChange={onChange}

16
components/typography/Link.tsx

@ -4,15 +4,12 @@ import type { BlockProps } from './Base';
import Base from './Base';
export interface LinkProps
extends BlockProps,
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'type'> {
extends BlockProps<'a'>,
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'type' | keyof BlockProps<'a'>> {
ellipsis?: boolean;
}
const Link: React.ForwardRefRenderFunction<HTMLElement, LinkProps> = (
{ ellipsis, rel, ...restProps },
ref,
) => {
const Link = React.forwardRef<HTMLElement, LinkProps>(({ ellipsis, rel, ...restProps }, ref) => {
warning(
typeof ellipsis !== 'object',
'Typography.Link',
@ -28,11 +25,10 @@ const Link: React.ForwardRefRenderFunction<HTMLElement, LinkProps> = (
rel: rel === undefined && restProps.target === '_blank' ? 'noopener noreferrer' : rel,
};
// https://github.com/ant-design/ant-design/issues/26622
// @ts-ignore
// @ts-expect-error: https://github.com/ant-design/ant-design/issues/26622
delete mergedProps.navigate;
return <Base {...mergedProps} ref={baseRef} ellipsis={!!ellipsis} component="a" />;
};
});
export default React.forwardRef(Link);
export default Link;

12
components/typography/Paragraph.tsx

@ -2,12 +2,12 @@ import * as React from 'react';
import type { BlockProps } from './Base';
import Base from './Base';
export interface ParagraphProps extends BlockProps {
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
}
export interface ParagraphProps
extends BlockProps<'div'>,
Omit<React.HTMLAttributes<HTMLDivElement>, 'type' | keyof BlockProps<'div'>> {}
const Paragraph: React.ForwardRefRenderFunction<HTMLDivElement, ParagraphProps> = (props, ref) => (
const Paragraph = React.forwardRef<HTMLElement, ParagraphProps>((props, ref) => (
<Base ref={ref} {...props} component="div" />
);
));
export default React.forwardRef(Paragraph);
export default Paragraph;

5
components/typography/Text.tsx

@ -4,9 +4,10 @@ import warning from '../_util/warning';
import type { BlockProps, EllipsisConfig } from './Base';
import Base from './Base';
export interface TextProps extends BlockProps {
export interface TextProps
extends BlockProps<'span'>,
Omit<React.HTMLAttributes<HTMLSpanElement>, 'type' | keyof BlockProps<'span'>> {
ellipsis?: boolean | Omit<EllipsisConfig, 'expandable' | 'rows' | 'onExpand'>;
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
}
const Text: React.ForwardRefRenderFunction<HTMLSpanElement, TextProps> = (

23
components/typography/Title.tsx

@ -6,17 +6,18 @@ import Base from './Base';
const TITLE_ELE_LIST = tupleNum(1, 2, 3, 4, 5);
export type TitleProps = Omit<
BlockProps & {
level?: typeof TITLE_ELE_LIST[number];
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
},
'strong'
>;
export interface TitleProps
extends Omit<BlockProps<'h1' | 'h2' | 'h3' | 'h4' | 'h5'>, 'strong'>,
Omit<
React.HTMLAttributes<HTMLHeadElement>,
'type' | keyof BlockProps<'h1' | 'h2' | 'h3' | 'h4' | 'h5'>
> {
level?: typeof TITLE_ELE_LIST[number];
}
const Title: React.ForwardRefRenderFunction<HTMLHeadingElement, TitleProps> = (props, ref) => {
const Title = React.forwardRef<HTMLElement, TitleProps>((props, ref) => {
const { level = 1, ...restProps } = props;
let component: string;
let component: keyof JSX.IntrinsicElements;
if (TITLE_ELE_LIST.indexOf(level) !== -1) {
component = `h${level}`;
@ -30,6 +31,6 @@ const Title: React.ForwardRefRenderFunction<HTMLHeadingElement, TitleProps> = (p
}
return <Base ref={ref} {...restProps} component={component} />;
};
});
export default React.forwardRef(Title);
export default Title;

97
components/typography/Typography.tsx

@ -1,72 +1,81 @@
import classNames from 'classnames';
import { composeRef } from 'rc-util/lib/ref';
import * as React from 'react';
import type { DirectionType } from '../config-provider';
import { ConfigContext } from '../config-provider';
import warning from '../_util/warning';
import useStyle from './style';
export interface TypographyProps {
export interface TypographyProps<C extends keyof JSX.IntrinsicElements>
extends React.HTMLAttributes<HTMLElement> {
id?: string;
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
/** @internal */
component?: C;
['aria-label']?: string;
direction?: DirectionType;
}
interface InternalTypographyProps extends TypographyProps {
component?: string;
interface InternalTypographyProps<C extends keyof JSX.IntrinsicElements>
extends TypographyProps<C> {
/** @deprecated Use `ref` directly if using React 16 */
setContentRef?: (node: HTMLElement) => void;
}
const Typography: React.ForwardRefRenderFunction<{}, InternalTypographyProps> = (
{
prefixCls: customizePrefixCls,
component = 'article',
className,
'aria-label': ariaLabel,
setContentRef,
children,
...restProps
},
ref,
) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const Typography = React.forwardRef<
HTMLElement,
InternalTypographyProps<keyof JSX.IntrinsicElements>
>(
(
{
prefixCls: customizePrefixCls,
component: Component = 'article',
className,
setContentRef,
children,
direction: typographyDirection,
...restProps
},
ref,
) => {
const { getPrefixCls, direction: contextDirection } = React.useContext(ConfigContext);
let mergedRef = ref;
if (setContentRef) {
warning(false, 'Typography', '`setContentRef` is deprecated. Please use `ref` instead.');
mergedRef = composeRef(ref, setContentRef);
}
const direction = typographyDirection ?? contextDirection;
const Component = component as any;
const prefixCls = getPrefixCls('typography', customizePrefixCls);
let mergedRef = ref;
if (setContentRef) {
warning(false, 'Typography', '`setContentRef` is deprecated. Please use `ref` instead.');
mergedRef = composeRef(ref, setContentRef);
}
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
const componentClassName = classNames(
prefixCls,
{
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
hashId,
);
return wrapSSR(
<Component className={componentClassName} aria-label={ariaLabel} ref={mergedRef} {...restProps}>
{children}
</Component>,
);
};
const prefixCls = getPrefixCls('typography', customizePrefixCls);
const componentClassName = classNames(
prefixCls,
{
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
hashId,
);
return wrapSSR(
// @ts-expect-error: Expression produces a union type that is too complex to represent.
<Component className={componentClassName} ref={mergedRef} {...restProps}>
{children}
</Component>,
);
},
);
const RefTypography = React.forwardRef(Typography);
if (process.env.NODE_ENV !== 'production') {
RefTypography.displayName = 'Typography';
Typography.displayName = 'Typography';
}
// es default export should use const instead of let
const ExportTypography = RefTypography as unknown as React.FC<TypographyProps>;
export default ExportTypography;
export default Typography;

4
components/typography/__tests__/__snapshots__/index.test.tsx.snap

@ -3,27 +3,23 @@
exports[`Typography rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-typography ant-typography-rtl"
direction="rtl"
/>
`;
exports[`Typography rtl render component should be rendered correctly in RTL direction 2`] = `
<article
class="ant-typography ant-typography-rtl"
direction="rtl"
/>
`;
exports[`Typography rtl render component should be rendered correctly in RTL direction 3`] = `
<h1
class="ant-typography ant-typography-rtl"
direction="rtl"
/>
`;
exports[`Typography rtl render component should be rendered correctly in RTL direction 4`] = `
<a
class="ant-typography ant-typography-rtl"
direction="rtl"
/>
`;

17
components/typography/__tests__/ellipsis.test.tsx

@ -382,21 +382,14 @@ describe('Typography.Ellipsis', () => {
it('js ellipsis should show aria-label', () => {
const { container: titleWrapper } = render(
<Base
component={undefined as unknown as string}
title="bamboo"
ellipsis={{ expandable: true }}
/>,
<Base component={undefined} title="bamboo" ellipsis={{ expandable: true }} />,
);
expect(titleWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual(
'bamboo',
);
const { container: tooltipWrapper } = render(
<Base
component={undefined as unknown as string}
ellipsis={{ expandable: true, tooltip: 'little' }}
/>,
<Base component={undefined} ellipsis={{ expandable: true, tooltip: 'little' }} />,
);
expect(tooltipWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual(
'little',
@ -426,11 +419,7 @@ describe('Typography.Ellipsis', () => {
const ref = React.createRef<any>();
const { container, baseElement } = render(
<Base
component={undefined as unknown as string}
ellipsis={{ tooltip: 'This is tooltip', rows: 2 }}
ref={ref}
>
<Base component={undefined} ellipsis={{ tooltip: 'This is tooltip', rows: 2 }} ref={ref}>
Ant Design, a design language for background applications, is refined by Ant UED Team.
</Base>,
);

17
docker-compose.yml

@ -1,17 +0,0 @@
version: '3'
services:
tests:
build:
context: .
dockerfile: Dockerfile.ui-test
volumes:
- './node_modules:/app/node_modules'
- './components:/app/components'
- './tests:/app/tests'
- './jest-stare:/app/jest-stare'
- './dist:/app/dist'
- '.jest.image.js:/app/.jest.image.js'
- './jest-puppeteer.config.js:/app/jest-puppeteer.config.js'
- './imageSnapshots:/app/imageSnapshots'
- './imageDiffSnapshots:/app/imageDiffSnapshots'
entrypoint: "npm run test-image:docker"

9
package.json

@ -91,8 +91,7 @@
"test-node": "npm run version && jest --config .jest.node.js --cache=false",
"tsc": "tsc --noEmit",
"site:test": "jest --config .jest.site.js --cache=false --force-exit",
"test-image": "npm run dist && docker-compose run tests",
"test-image:docker": "node node_modules/puppeteer/install.js && jest --config .jest.image.js --no-cache -i",
"test-image": "npm run dist && jest --config .jest.image.js --no-cache -i -u",
"argos": "node ./scripts/argos-upload.js",
"version": "node ./scripts/generate-version",
"install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16",
@ -134,7 +133,7 @@
"rc-motion": "^2.6.1",
"rc-notification": "~5.0.0-alpha.9",
"rc-pagination": "~3.1.17",
"rc-picker": "~2.6.10",
"rc-picker": "~2.6.11",
"rc-progress": "~3.3.2",
"rc-rate": "~2.9.0",
"rc-resize-observer": "^1.2.0",
@ -145,7 +144,7 @@
"rc-switch": "~4.0.0",
"rc-table": "~7.26.0",
"rc-tabs": "~12.2.0",
"rc-textarea": "~0.4.3",
"rc-textarea": "~0.4.5",
"rc-tooltip": "~5.2.0",
"rc-tree": "~5.7.0",
"rc-tree-select": "~5.5.0",
@ -188,7 +187,7 @@
"@types/warning": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"antd-img-crop": "4.2.5",
"antd-img-crop": "^4.2.8",
"array-move": "^4.0.0",
"babel-plugin-add-react-displayname": "^0.0.5",
"bisheng": "^3.7.0-alpha.4",

2
scripts/argos-upload.js

@ -47,7 +47,7 @@ async function run() {
token: process.env.ARGOS_TOKEN,
parallel: {
total: chunks.length,
nonce: process.env.ARGOS_PARALLEL_NONCE,
nonce: process.env.ARGOS_PARALLEL_NONCE || process.env.CIRCLE_BUILD_NUM,
},
});
// eslint-disable-next-line no-console -- pipe stdout

1
scripts/print-changelog.js

@ -36,6 +36,7 @@ const MAINTAINERS = [
'kerm1it',
'madccc',
'MadCcc',
'li-jia-nan',
].map(author => author.toLowerCase());
const cwd = process.cwd();

37
site/theme/template/Content/Demo/index.jsx

@ -48,12 +48,13 @@ class Demo extends React.Component {
}
shouldComponentUpdate(nextProps, nextState) {
const { codeExpand, copied, copyTooltipOpen } = this.state;
const { codeExpand, copied, copyTooltipOpen, codeType } = this.state;
const { expand, theme, showRiddleButton } = this.props;
return (
(codeExpand || expand) !== (nextState.codeExpand || nextProps.expand) ||
copied !== nextState.copied ||
copyTooltipOpen !== nextState.copyTooltipOpen ||
codeType !== nextState.copyTooltipOpen ||
nextProps.theme !== theme ||
nextProps.showRiddleButton !== showRiddleButton
);
@ -141,7 +142,7 @@ class Demo extends React.Component {
theme,
showRiddleButton,
} = props;
const { copied, copyTooltipOpen } = state;
const { copied, copyTooltipOpen, codeType } = state;
if (!this.liveDemo) {
this.liveDemo = meta.iframe ? (
<BrowserFrame>
@ -183,7 +184,18 @@ class Demo extends React.Component {
</body>
</html>`;
const tsconfig = `{
"compilerOptions": {
"jsx": "react-jsx",
"target": "esnext",
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "node",
}
}`;
const [sourceCode, sourceCodeTyped] = this.getSourceCode();
const suffix = codeType === 'tsx' ? 'tsx' : 'js';
const dependencies = sourceCode.split('\n').reduce(
(acc, line) => {
@ -203,6 +215,10 @@ class Demo extends React.Component {
);
dependencies['@ant-design/icons'] = 'latest';
if (suffix === 'tsx') {
dependencies['@types/react'] = '^18.0.0';
dependencies['@types/react-dom'] = '^18.0.0';
}
dependencies.react = '^18.0.0';
dependencies['react-dom'] = '^18.0.0';
@ -262,7 +278,7 @@ class Demo extends React.Component {
};
// Reorder source code
let parsedSourceCode = sourceCode;
let parsedSourceCode = suffix === 'tsx' ? sourceCodeTyped : sourceCode;
let importReactContent = "import React from 'react';";
const importReactReg = /import React(\D*)from 'react';/;
@ -315,8 +331,8 @@ createRoot(document.getElementById('container')).render(<Demo />);
files: {
'package.json': { content: codesandboxPackage },
'index.css': { content: indexCssContent },
'index.js': { content: indexJsContent },
'demo.js': { content: demoJsContent },
[`index.${suffix}`]: { content: indexJsContent },
[`demo.${suffix}`]: { content: demoJsContent },
'index.html': {
content: html,
},
@ -328,11 +344,14 @@ createRoot(document.getElementById('container')).render(<Demo />);
dependencies,
files: {
'index.css': indexCssContent,
'index.js': indexJsContent,
'demo.js': demoJsContent,
[`index.${suffix}`]: indexJsContent,
[`demo.${suffix}`]: demoJsContent,
'index.html': html,
},
};
if (suffix === 'tsx') {
stackblitzPrefillConfig.files['tsconfig.json'] = tsconfig;
}
let codeBox = (
<section className={codeBoxClass} id={meta.id}>
@ -418,7 +437,9 @@ createRoot(document.getElementById('container')).render(<Demo />);
className="code-box-code-action"
onClick={() => {
this.track({ type: 'stackblitz', demo: meta.id });
stackblitzSdk.openProject(stackblitzPrefillConfig);
stackblitzSdk.openProject(stackblitzPrefillConfig, {
openFile: [`demo.${suffix}`],
});
}}
>
<ThunderboltOutlined className="code-box-stackblitz" />

Loading…
Cancel
Save