feat: Anchor component changed to data-driven (#39034)

* feat: Anchor component changed to data-driven

* test: add test cases for data driven items

* fix: type

* chore: mark deprecated for anchor children prop

* docs: add items description

* test: update snapshot

* docs: demos changed to data-driven

* docs: Keep the old jsx syntax demo for debugging
This commit is contained in:
Yuki Zhang 2022-11-30 15:55:43 +08:00 committed by GitHub
parent 7037b470c0
commit 51da7e3dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 556 additions and 63 deletions

View File

@ -6,10 +6,18 @@ 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 AnchorContext from './context';
import type { AnchorLinkBaseProps } from './AnchorLink';
import AnchorLink from './AnchorLink';
import useStyle from './style';
export interface AnchorLinkItemProps extends AnchorLinkBaseProps {
key: React.Key;
children?: AnchorLinkItemProps[];
}
export type AnchorContainer = HTMLElement | Window;
function getDefaultContainer() {
@ -45,6 +53,9 @@ export interface AnchorProps {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
/**
* @deprecated Please use `items` instead.
*/
children?: React.ReactNode;
offsetTop?: number;
bounds?: number;
@ -61,6 +72,7 @@ export interface AnchorProps {
targetOffset?: number;
/** Listening event when scrolling change active link */
onChange?: (currentActiveLink: string) => void;
items?: AnchorLinkItemProps[];
}
interface InternalAnchorProps extends AnchorProps {
@ -100,6 +112,7 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
affix = true,
showInkInFixed = false,
children,
items,
bounds,
targetOffset,
onClick,
@ -108,6 +121,11 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
getCurrentAnchor,
} = props;
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
warning(!children, 'Anchor', '`Anchor children` is deprecated. Please use `items` instead.');
}
const [links, setLinks] = React.useState<string[]>([]);
const [activeLink, setActiveLink] = React.useState<string | null>(null);
const activeLinkRef = React.useRef<string | null>(activeLink);
@ -257,13 +275,22 @@ const AnchorContent: React.FC<InternalAnchorProps> = (props) => {
...style,
};
const createNestedLink = (options?: AnchorLinkItemProps[]) =>
Array.isArray(options)
? options.map((item) => (
<AnchorLink {...item} key={item.key}>
{createNestedLink(item.children)}
</AnchorLink>
))
: null;
const anchorContent = (
<div ref={wrapperRef} className={wrapperClass} style={wrapperStyle}>
<div className={anchorClass}>
<div className={`${prefixCls}-ink`}>
<span className={inkClass} ref={spanLinkNode} />
</div>
{children}
{'items' in props ? createNestedLink(items) : children}
</div>
</div>
);

View File

@ -5,15 +5,18 @@ import { ConfigConsumer } from '../config-provider';
import type { AntAnchor } from './Anchor';
import AnchorContext from './context';
export interface AnchorLinkProps {
export interface AnchorLinkBaseProps {
prefixCls?: string;
href: string;
target?: string;
title: React.ReactNode;
children?: React.ReactNode;
className?: string;
}
export interface AnchorLinkProps extends AnchorLinkBaseProps {
children?: React.ReactNode;
}
const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
const { href = '#', title, prefixCls: customizePrefixCls, children, className, target } = props;

View File

@ -432,4 +432,64 @@ describe('Anchor Render', () => {
}).not.toThrow();
});
});
it('renders items correctly', () => {
const { container, asFragment } = render(
<Anchor
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Item Basic Demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
children: [
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
],
},
]}
/>,
);
expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(5);
const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!)
.slice(1)
.map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title'));
expect((linkTitles[0] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic');
expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static');
expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#api');
expect(asFragment().firstChild).toMatchSnapshot();
expect(
(
container.querySelector(
'.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link-title',
) as HTMLAnchorElement
)?.href,
).toContain('#anchor-props');
expect(
(
container.querySelector(
'.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link .ant-anchor-link-title',
) as HTMLAnchorElement
)?.href,
).toContain('#link-props');
});
});

View File

@ -0,0 +1,81 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Anchor Render renders items correctly 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<div
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Item Basic Demo"
>
Item Basic Demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -154,6 +154,86 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx extend context
</div>
`;
exports[`renders ./components/anchor/demo/legacy-anchor.tsx extend context correctly 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<div
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/anchor/demo/onChange.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"

View File

@ -154,6 +154,86 @@ exports[`renders ./components/anchor/demo/customizeHighlight.tsx correctly 1`] =
</div>
`;
exports[`renders ./components/anchor/demo/legacy-anchor.tsx correctly 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<div
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/anchor/demo/onChange.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"

View File

@ -1,17 +1,38 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const App: React.FC = () => (
<Anchor>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
<Anchor
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -1,19 +1,42 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const getCurrentAnchor = () => '#components-anchor-demo-static';
const App: React.FC = () => (
<Anchor affix={false} getCurrentAnchor={getCurrentAnchor}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
<Anchor
affix={false}
getCurrentAnchor={getCurrentAnchor}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
Debug usage
## en-US
Debug usage

View File

@ -0,0 +1,17 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const App: React.FC = () => (
<Anchor>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
);
export default App;

View File

@ -1,21 +1,44 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const onChange = (link: string) => {
console.log('Anchor:OnChange', link);
};
const App: React.FC = () => (
<Anchor affix={false} onChange={onChange}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
<Anchor
affix={false}
onChange={onChange}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -1,8 +1,6 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const handleClick = (
e: React.MouseEvent<HTMLElement>,
link: {
@ -15,14 +13,39 @@ const handleClick = (
};
const App: React.FC = () => (
<Anchor affix={false} onClick={handleClick}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
<Anchor
affix={false}
onClick={handleClick}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -1,17 +1,39 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const App: React.FC = () => (
<Anchor affix={false}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
<Anchor
affix={false}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -1,8 +1,6 @@
import React, { useEffect, useState } from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const App: React.FC = () => {
const [targetOffset, setTargetOffset] = useState<number | undefined>(undefined);
@ -11,14 +9,38 @@ const App: React.FC = () => {
}, []);
return (
<Anchor targetOffset={targetOffset}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
<Anchor
targetOffset={targetOffset}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
};

View File

@ -28,6 +28,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">Set Anchor scroll offset</code>
<code src="./demo/onChange.tsx">Listening for anchor link change</code>
<code src="./demo/legacy-anchor.tsx" debug>Deprecated JSX demo</code>
## API
@ -44,6 +45,7 @@ For displaying anchor hyperlinks on page and jumping between them.
| targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetOffset) | number | - | |
| onChange | Listening for anchor link change | (currentActiveLink: string) => void | | |
| onClick | Set the handler to handle `click` event | function(e: Event, link: Object) | - | |
| items | Data configuration option content, support nesting through children | { href, title, target, children }\[] | - | |
### Link Props

View File

@ -29,6 +29,7 @@ group:
<code src="./demo/customizeHighlight.tsx">自定义锚点高亮</code>
<code src="./demo/targetOffset.tsx">设置锚点滚动偏移量</code>
<code src="./demo/onChange.tsx">监听锚点链接改变</code>
<code src="./demo/legacy-anchor.tsx" debug>废弃的 JSX 示例</code>
## API
@ -45,6 +46,7 @@ group:
| targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#components-anchor-demo-targetOffset) | number | - | |
| onChange | 监听锚点链接改变 | (currentActiveLink: string) => void | - | |
| onClick | `click` 事件的 handler | function(e: Event, link: Object) | - | |
| items | 数据化配置选项内容,支持通过 children 嵌套 | { href, title, target, children }\[] | - | |
### Link Props