Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Anchor): allow replacing in anchor #43006

Merged
10 changes: 6 additions & 4 deletions components/anchor/Anchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import useEvent from 'rc-util/lib/hooks/useEvent';
import * as React from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';

import Affix from '../affix';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import getScroll from '../_util/getScroll';
import scrollTo from '../_util/scrollTo';
import warning from '../_util/warning';
import Affix from '../affix';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import type { AnchorLinkBaseProps } from './AnchorLink';
import AnchorLink from './AnchorLink';
import AnchorContext from './context';
Expand Down Expand Up @@ -77,6 +77,7 @@ export interface AnchorProps {
onChange?: (currentActiveLink: string) => void;
items?: AnchorLinkItemProps[];
direction?: AnchorDirection;
replace?: boolean;
}

interface InternalAnchorProps extends AnchorProps {
Expand Down Expand Up @@ -127,6 +128,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
onChange,
getContainer,
getCurrentAnchor,
replace,
} = props;

// =================== Warning =====================
Expand Down Expand Up @@ -296,7 +298,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
const createNestedLink = (options?: AnchorLinkItemProps[]) =>
Array.isArray(options)
? options.map((item) => (
<AnchorLink {...item} key={item.key}>
<AnchorLink replace={replace} {...item} key={item.key}>
{anchorDirection === 'vertical' && createNestedLink(item.children)}
</AnchorLink>
))
Expand Down
17 changes: 15 additions & 2 deletions components/anchor/AnchorLink.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { AntAnchor } from './Anchor';
import AnchorContext from './context';

Expand All @@ -11,14 +11,23 @@ export interface AnchorLinkBaseProps {
target?: string;
title: React.ReactNode;
className?: string;
replace?: boolean;
}

export interface AnchorLinkProps extends AnchorLinkBaseProps {
children?: React.ReactNode;
}

const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
const { href = '#', title, prefixCls: customizePrefixCls, children, className, target } = props;
const {
href,
title,
prefixCls: customizePrefixCls,
children,
className,
target,
replace,
} = props;

const context = React.useContext<AntAnchor | undefined>(AnchorContext);

Expand All @@ -32,6 +41,10 @@ const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
}, [href]);

const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (replace) {
e.preventDefault();
window.location.replace(href);
}
onClick?.(e, { title, href });
scrollTo?.(href);
};
Expand Down
17 changes: 17 additions & 0 deletions components/anchor/__tests__/Anchor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ const getHashUrl = () => `Anchor-API-${idCounter++}`;

jest.mock('scroll-into-view-if-needed', () => jest.fn());

Object.defineProperty(window, 'location', {
value: {
replace: jest.fn(),
},
});

describe('Anchor Render', () => {
const getBoundingClientRectMock = jest.spyOn(
HTMLHeadingElement.prototype,
Expand Down Expand Up @@ -344,6 +350,17 @@ describe('Anchor Render', () => {
expect(link).toEqual({ href, title });
});

it('replaces item href in browser history', () => {
const hash = getHashUrl();

const href = `#${hash}`;
const title = hash;
const { container } = render(<Anchor replace items={[{ key: hash, href, title }]} />);

fireEvent.click(container.querySelector(`a[href="${href}"]`)!);
expect(window.location.replace).toHaveBeenCalledWith(href);
});

it('onChange event', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
Expand Down
79 changes: 79 additions & 0 deletions components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,85 @@ exports[`renders components/anchor/demo/onClick.tsx extend context correctly 1`]
</div>
`;

exports[`renders components/anchor/demo/replace.tsx extend context correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height: 100vh; background: rgba(255, 0, 0, 0.02);"
/>
<div
id="part-2"
style="height: 100vh; background: rgba(0, 255, 0, 0.02);"
/>
<div
id="part-3"
style="height: 100vh; background: rgba(0, 0, 255, 0.02);"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="top: 0px; height: 0px;"
/>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

exports[`renders components/anchor/demo/static.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
Expand Down
78 changes: 78 additions & 0 deletions components/anchor/__tests__/__snapshots__/demo.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,84 @@ exports[`renders components/anchor/demo/onClick.tsx correctly 1`] = `
</div>
`;

exports[`renders components/anchor/demo/replace.tsx correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height:100vh;background:rgba(255,0,0,0.02)"
/>
<div
id="part-2"
style="height:100vh;background:rgba(0,255,0,0.02)"
/>
<div
id="part-3"
style="height:100vh;background:rgba(0,0,255,0.02)"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

exports[`renders components/anchor/demo/static.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
Expand Down
7 changes: 7 additions & 0 deletions components/anchor/demo/replace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## zh-CN

替换浏览器历史记录中的路径,后退按钮将返回到上一页而不是上一个锚点。

## en-US

Replace path in browser history, so back button returns to previous page instead of previous anchor item.
36 changes: 36 additions & 0 deletions components/anchor/demo/replace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Anchor, Col, Row } from 'antd';
import React from 'react';

const App: React.FC = () => (
<Row>
<Col span={16}>
<div id="part-1" style={{ height: '100vh', background: 'rgba(255,0,0,0.02)' }} />
<div id="part-2" style={{ height: '100vh', background: 'rgba(0,255,0,0.02)' }} />
<div id="part-3" style={{ height: '100vh', background: 'rgba(0,0,255,0.02)' }} />
</Col>
<Col span={8}>
<Anchor
replace
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
{
key: 'part-2',
href: '#part-2',
title: 'Part 2',
},
{
key: 'part-3',
href: '#part-3',
title: 'Part 3',
},
]}
/>
</Col>
</Row>
);

export default App;
3 changes: 3 additions & 0 deletions components/anchor/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ For displaying anchor hyperlinks on page and jumping between them.
<code src="./demo/customizeHighlight.tsx">Customize the anchor highlight</code>
<code src="./demo/targetOffset.tsx" iframe="200">Set Anchor scroll offset</code>
<code src="./demo/onChange.tsx">Listening for anchor link change</code>
<code src="./demo/replace.tsx" iframe="200">Replace href in history</code>
<code src="./demo/legacy-anchor.tsx" debug>Deprecated JSX demo</code>
<code src="./demo/component-token.tsx" debug>Component Token</code>

Expand All @@ -49,6 +50,7 @@ For displaying anchor hyperlinks on page and jumping between them.
| onClick | Set the handler to handle `click` event | (e: MouseEvent, link: object) => void | - | |
| items | Data configuration option content, support nesting through children | { key, href, title, target, children }\[] [see](#anchoritem) | - | 5.1.0 |
| direction | Set Anchor direction | `vertical` \| `horizontal` | `vertical` | 5.2.0 |
| replace | Replace items' href in browser history instead of pushing it | boolean | false | 5.7.0 |

### AnchorItem

Expand All @@ -59,6 +61,7 @@ For displaying anchor hyperlinks on page and jumping between them.
| target | Specifies where to display the linked URL | string | | |
| title | The content of hyperlink | ReactNode | | |
| children | Nested Anchor Link, `Attention: This attribute does not support horizontal orientation` | [AnchorItem](#anchoritem)\[] | - | |
| replace | Replace item href in browser history instead of pushing it | boolean | false | 5.7.0 |

### Link Props

Expand Down
3 changes: 3 additions & 0 deletions components/anchor/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ group:
<code src="./demo/customizeHighlight.tsx">自定义锚点高亮</code>
<code src="./demo/targetOffset.tsx" iframe="200">设置锚点滚动偏移量</code>
<code src="./demo/onChange.tsx">监听锚点链接改变</code>
<code src="./demo/replace.tsx" iframe="200">替换历史中的 href</code>
<code src="./demo/legacy-anchor.tsx" debug>废弃的 JSX 示例</code>
<code src="./demo/component-token.tsx" debug>组件 Token</code>

Expand All @@ -50,6 +51,7 @@ group:
| onClick | `click` 事件的 handler | (e: MouseEvent, link: object) => void | - | |
| items | 数据化配置选项内容,支持通过 children 嵌套 | { key, href, title, target, children }\[] [具体见](#anchoritem) | - | 5.1.0 |
| direction | 设置导航方向 | `vertical` \| `horizontal` | `vertical` | 5.2.0 |
| replace | 替换浏览器历史记录中项目的 href 而不是推送它 | boolean | false | 5.7.0 |

### AnchorItem

Expand All @@ -60,6 +62,7 @@ group:
| target | 该属性指定在何处显示链接的资源 | string | - | |
| title | 文字内容 | ReactNode | - | |
| children | 嵌套的 Anchor Link,`注意:水平方向该属性不支持` | [AnchorItem](#anchoritem)\[] | - | |
| replace | 替换浏览器历史记录中的项目 href 而不是推送它 | boolean | false | 5.7.0 |

### Link Props

Expand Down