Skip to content

Commit 8932aff

Browse files
feat(anchor): add direction action (#6447)
* refactor(anchor): direction show * refactor(anchor): update anchor css * feat(anchor): update demo * test(anchor): update demo test snap * feat(anchor): update docs * Update index.zh-CN.md * Update index.en-US.md --------- Co-authored-by: tangjinzhou <[email protected]>
1 parent de00607 commit 8932aff

File tree

9 files changed

+312
-98
lines changed

9 files changed

+312
-98
lines changed

components/anchor/Anchor.tsx

+53-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
22
import {
3+
watch,
34
defineComponent,
45
nextTick,
56
onBeforeUnmount,
@@ -9,6 +10,7 @@ import {
910
ref,
1011
computed,
1112
} from 'vue';
13+
import scrollIntoView from 'scroll-into-view-if-needed';
1214
import classNames from '../_util/classNames';
1315
import addEventListener from '../vc-util/Dom/addEventListener';
1416
import Affix from '../affix';
@@ -20,13 +22,18 @@ import useStyle from './style';
2022
import type { AnchorLinkProps } from './AnchorLink';
2123
import AnchorLink from './AnchorLink';
2224
import type { Key } from '../_util/type';
25+
import PropTypes from '../_util/vue-types';
26+
import devWarning from '../vc-util/devWarning';
2327

2428
export interface AnchorLinkItemProps extends AnchorLinkProps {
2529
key: Key;
2630
class?: String;
2731
style?: CSSProperties;
2832
children?: AnchorLinkItemProps[];
2933
}
34+
35+
export type AnchorDirection = 'vertical' | 'horizontal';
36+
3037
function getDefaultContainer() {
3138
return window;
3239
}
@@ -73,6 +80,7 @@ export const anchorProps = () => ({
7380
type: Array as PropType<AnchorLinkItemProps[]>,
7481
default: undefined as AnchorLinkItemProps[],
7582
},
83+
direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'),
7684
onChange: Function as PropType<(currentActiveLink: string) => void>,
7785
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
7886
});
@@ -93,6 +101,24 @@ export default defineComponent({
93101
props: anchorProps(),
94102
setup(props, { emit, attrs, slots, expose }) {
95103
const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
104+
const anchorDirection = computed(() => props.direction ?? 'vertical');
105+
106+
if (process.env.NODE_ENV !== 'production') {
107+
devWarning(
108+
typeof slots.default !== 'function',
109+
'Anchor',
110+
'`Anchor children` is deprecated. Please use `items` instead.',
111+
);
112+
}
113+
114+
if (process.env.NODE_ENV !== 'production') {
115+
devWarning(
116+
!(anchorDirection.value === 'horizontal' && props.items?.some(n => 'children' in n)),
117+
'Anchor',
118+
'`Anchor items#children` is not supported when `Anchor` direction is horizontal.',
119+
);
120+
}
121+
96122
const spanLinkNode = ref<HTMLSpanElement>(null);
97123
const anchorRef = ref();
98124
const state = reactive<AnchorState>({
@@ -184,12 +210,21 @@ export default defineComponent({
184210
};
185211

186212
const updateInk = () => {
187-
const linkNode = anchorRef.value.getElementsByClassName(
188-
`${prefixCls.value}-link-title-active`,
189-
)[0];
213+
const linkNode = anchorRef.value.querySelector(`.${prefixCls.value}-link-title-active`);
190214
if (linkNode && spanLinkNode.value) {
191-
spanLinkNode.value.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
192-
spanLinkNode.value.style.height = `${linkNode.clientHeight}px`;
215+
const horizontalAnchor = anchorDirection.value === 'horizontal';
216+
spanLinkNode.value.style.top = horizontalAnchor
217+
? ''
218+
: `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
219+
spanLinkNode.value.style.height = horizontalAnchor ? '' : `${linkNode.clientHeight}px`;
220+
spanLinkNode.value.style.left = horizontalAnchor ? `${linkNode.offsetLeft}px` : '';
221+
spanLinkNode.value.style.width = horizontalAnchor ? `${linkNode.clientWidth}px` : '';
222+
if (horizontalAnchor) {
223+
scrollIntoView(linkNode, {
224+
scrollMode: 'if-needed',
225+
block: 'nearest',
226+
});
227+
}
193228
}
194229
};
195230

@@ -210,6 +245,7 @@ export default defineComponent({
210245
handleClick: (e, info) => {
211246
emit('click', e, info);
212247
},
248+
direction: anchorDirection,
213249
});
214250

215251
onMounted(() => {
@@ -237,23 +273,31 @@ export default defineComponent({
237273
}
238274
updateInk();
239275
});
276+
277+
watch([anchorDirection, getCurrentAnchor, state.links, activeLink], () => {
278+
updateInk();
279+
});
280+
240281
const createNestedLink = (options?: AnchorLinkItemProps[]) =>
241282
Array.isArray(options)
242283
? options.map(item => (
243284
<AnchorLink {...item} key={item.key}>
244-
{createNestedLink(item.children)}
285+
{anchorDirection.value === 'vertical' ? createNestedLink(item.children) : null}
245286
</AnchorLink>
246287
))
247288
: null;
289+
248290
const [wrapSSR, hashId] = useStyle(prefixCls);
291+
249292
return () => {
250293
const { offsetTop, affix, showInkInFixed } = props;
251294
const pre = prefixCls.value;
252-
const inkClass = classNames(`${pre}-ink-ball`, {
253-
[`${pre}-ink-ball-visible`]: activeLink.value,
295+
const inkClass = classNames(`${pre}-ink`, {
296+
[`${pre}-ink-visible`]: activeLink.value,
254297
});
255298

256299
const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, {
300+
[`${pre}-wrapper-horizontal`]: anchorDirection.value === 'horizontal',
257301
[`${pre}-rtl`]: direction.value === 'rtl',
258302
});
259303

@@ -268,9 +312,7 @@ export default defineComponent({
268312
const anchorContent = (
269313
<div class={wrapperClass} style={wrapperStyle} ref={anchorRef}>
270314
<div class={anchorClass}>
271-
<div class={`${pre}-ink`}>
272-
<span class={inkClass} ref={spanLinkNode} />
273-
</div>
315+
<span class={inkClass} ref={spanLinkNode} />
274316
{Array.isArray(props.items) ? createNestedLink(props.items) : slots.default?.()}
275317
</div>
276318
</div>

components/anchor/__tests__/__snapshots__/demo.test.js.snap

+51-31
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,28 @@
22

33
exports[`renders ./components/anchor/demo/basic.vue correctly 1`] = `
44
<div>
5-
<div class="">
6-
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
7-
<div class="ant-anchor">
8-
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
9-
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
5+
<!---->
6+
<div class="css-dev-only-do-not-override-1tii49m">
7+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
8+
<div class="ant-anchor"><span class="ant-anchor-ink"></span>
9+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#part-1" title="Part 1">Part 1</a>
1010
<!---->
1111
</div>
12-
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-static" title="Static demo">Static demo</a>
12+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#part-2" title="Part 2">Part 2</a>
1313
<!---->
1414
</div>
15-
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo with Target" target="_blank">Basic demo with Target</a>
15+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#part-3" title="Part 3">Part 3</a>
1616
<!---->
1717
</div>
18-
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#API" title="API">API</a>
19-
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#Anchor-Props" title="Anchor Props">Anchor Props</a>
20-
<!---->
21-
</div>
22-
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#Link-Props" title="Link Props">Link Props</a>
23-
<!---->
24-
</div>
25-
</div>
2618
</div>
2719
</div>
2820
</div>
2921
</div>
3022
`;
3123

3224
exports[`renders ./components/anchor/demo/customizeHighlight.vue correctly 1`] = `
33-
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
34-
<div class="ant-anchor ant-anchor-fixed">
35-
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
25+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
26+
<div class="ant-anchor ant-anchor-fixed"><span class="ant-anchor-ink"></span>
3627
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
3728
<!---->
3829
</div>
@@ -51,10 +42,41 @@ exports[`renders ./components/anchor/demo/customizeHighlight.vue correctly 1`] =
5142
</div>
5243
`;
5344

45+
exports[`renders ./components/anchor/demo/horizontal.vue correctly 1`] = `
46+
<div>
47+
<div>
48+
<!---->
49+
<div class="css-dev-only-do-not-override-1tii49m">
50+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper ant-anchor-wrapper-horizontal" style="max-height: 100vh;">
51+
<div class="ant-anchor"><span class="ant-anchor-ink"></span>
52+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-1" title="Part 1">Part 1</a>
53+
<!---->
54+
</div>
55+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-2" title="Part 2">Part 2</a>
56+
<!---->
57+
</div>
58+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-3" title="Part 3">Part 3</a>
59+
<!---->
60+
</div>
61+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-4" title="Part 4">Part 4</a>
62+
<!---->
63+
</div>
64+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-5" title="Part 5">Part 5</a>
65+
<!---->
66+
</div>
67+
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-6" title="Part 6">Part 6</a>
68+
<!---->
69+
</div>
70+
</div>
71+
</div>
72+
</div>
73+
</div>
74+
</div>
75+
`;
76+
5477
exports[`renders ./components/anchor/demo/onChange.vue correctly 1`] = `
55-
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
56-
<div class="ant-anchor ant-anchor-fixed">
57-
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
78+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
79+
<div class="ant-anchor ant-anchor-fixed"><span class="ant-anchor-ink"></span>
5880
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
5981
<!---->
6082
</div>
@@ -74,9 +96,8 @@ exports[`renders ./components/anchor/demo/onChange.vue correctly 1`] = `
7496
`;
7597

7698
exports[`renders ./components/anchor/demo/onClick.vue correctly 1`] = `
77-
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
78-
<div class="ant-anchor ant-anchor-fixed">
79-
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
99+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
100+
<div class="ant-anchor ant-anchor-fixed"><span class="ant-anchor-ink"></span>
80101
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
81102
<!---->
82103
</div>
@@ -96,9 +117,8 @@ exports[`renders ./components/anchor/demo/onClick.vue correctly 1`] = `
96117
`;
97118

98119
exports[`renders ./components/anchor/demo/static.vue correctly 1`] = `
99-
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
100-
<div class="ant-anchor ant-anchor-fixed">
101-
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
120+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
121+
<div class="ant-anchor ant-anchor-fixed"><span class="ant-anchor-ink"></span>
102122
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
103123
<!---->
104124
</div>
@@ -119,10 +139,10 @@ exports[`renders ./components/anchor/demo/static.vue correctly 1`] = `
119139

120140
exports[`renders ./components/anchor/demo/targetOffset.vue correctly 1`] = `
121141
<div>
122-
<div class="">
123-
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
124-
<div class="ant-anchor">
125-
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
142+
<!---->
143+
<div class="css-dev-only-do-not-override-1tii49m">
144+
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
145+
<div class="ant-anchor"><span class="ant-anchor-ink"></span>
126146
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
127147
<!---->
128148
</div>

components/anchor/context.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { Ref, InjectionKey } from 'vue';
1+
import type { Ref, InjectionKey, ComputedRef } from 'vue';
2+
import type { AnchorDirection } from './Anchor';
23
import { computed, inject, provide } from 'vue';
34

45
export interface AnchorContext {
@@ -7,6 +8,7 @@ export interface AnchorContext {
78
activeLink: Ref<string>;
89
scrollTo: (link: string) => void;
910
handleClick: (e: Event, info: { title: any; href: string }) => void;
11+
direction: ComputedRef<AnchorDirection>;
1012
}
1113

1214
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -25,6 +27,7 @@ const useInjectAnchor = () => {
2527
scrollTo: noop,
2628
activeLink: computed(() => ''),
2729
handleClick: noop,
30+
direction: computed(() => 'vertical'),
2831
} as AnchorContext);
2932
};
3033

components/anchor/demo/basic.vue

+19-13
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,23 @@ The simplest usage.
1616
</docs>
1717

1818
<template>
19-
<a-anchor>
20-
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" />
21-
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" />
22-
<a-anchor-link
23-
href="#components-anchor-demo-basic"
24-
title="Basic demo with Target"
25-
target="_blank"
26-
/>
27-
<a-anchor-link href="#API" title="API">
28-
<a-anchor-link href="#Anchor-Props" title="Anchor Props" />
29-
<a-anchor-link href="#Link-Props" title="Link Props" />
30-
</a-anchor-link>
31-
</a-anchor>
19+
<a-anchor
20+
:items="[
21+
{
22+
key: 'part-1',
23+
href: '#part-1',
24+
title: 'Part 1',
25+
},
26+
{
27+
key: 'part-2',
28+
href: '#part-2',
29+
title: 'Part 2',
30+
},
31+
{
32+
key: 'part-3',
33+
href: '#part-3',
34+
title: 'Part 3',
35+
},
36+
]"
37+
/>
3238
</template>

0 commit comments

Comments
 (0)