Skip to content

Commit

Permalink
feat(anchor): add direction action (#6447)
Browse files Browse the repository at this point in the history
* 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 <415800467@qq.com>
  • Loading branch information
CCherry07 and tangjinzhou authored Apr 21, 2023
1 parent de00607 commit 8932aff
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 98 deletions.
64 changes: 53 additions & 11 deletions components/anchor/Anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import {
watch,
defineComponent,
nextTick,
onBeforeUnmount,
Expand All @@ -9,6 +10,7 @@ import {
ref,
computed,
} from 'vue';
import scrollIntoView from 'scroll-into-view-if-needed';
import classNames from '../_util/classNames';
import addEventListener from '../vc-util/Dom/addEventListener';
import Affix from '../affix';
Expand All @@ -20,13 +22,18 @@ import useStyle from './style';
import type { AnchorLinkProps } from './AnchorLink';
import AnchorLink from './AnchorLink';
import type { Key } from '../_util/type';
import PropTypes from '../_util/vue-types';
import devWarning from '../vc-util/devWarning';

export interface AnchorLinkItemProps extends AnchorLinkProps {
key: Key;
class?: String;
style?: CSSProperties;
children?: AnchorLinkItemProps[];
}

export type AnchorDirection = 'vertical' | 'horizontal';

function getDefaultContainer() {
return window;
}
Expand Down Expand Up @@ -73,6 +80,7 @@ export const anchorProps = () => ({
type: Array as PropType<AnchorLinkItemProps[]>,
default: undefined as AnchorLinkItemProps[],
},
direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'),
onChange: Function as PropType<(currentActiveLink: string) => void>,
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
});
Expand All @@ -93,6 +101,24 @@ export default defineComponent({
props: anchorProps(),
setup(props, { emit, attrs, slots, expose }) {
const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
const anchorDirection = computed(() => props.direction ?? 'vertical');

if (process.env.NODE_ENV !== 'production') {
devWarning(
typeof slots.default !== 'function',
'Anchor',
'`Anchor children` is deprecated. Please use `items` instead.',
);
}

if (process.env.NODE_ENV !== 'production') {
devWarning(
!(anchorDirection.value === 'horizontal' && props.items?.some(n => 'children' in n)),
'Anchor',
'`Anchor items#children` is not supported when `Anchor` direction is horizontal.',
);
}

const spanLinkNode = ref<HTMLSpanElement>(null);
const anchorRef = ref();
const state = reactive<AnchorState>({
Expand Down Expand Up @@ -184,12 +210,21 @@ export default defineComponent({
};

const updateInk = () => {
const linkNode = anchorRef.value.getElementsByClassName(
`${prefixCls.value}-link-title-active`,
)[0];
const linkNode = anchorRef.value.querySelector(`.${prefixCls.value}-link-title-active`);
if (linkNode && spanLinkNode.value) {
spanLinkNode.value.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
spanLinkNode.value.style.height = `${linkNode.clientHeight}px`;
const horizontalAnchor = anchorDirection.value === 'horizontal';
spanLinkNode.value.style.top = horizontalAnchor
? ''
: `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
spanLinkNode.value.style.height = horizontalAnchor ? '' : `${linkNode.clientHeight}px`;
spanLinkNode.value.style.left = horizontalAnchor ? `${linkNode.offsetLeft}px` : '';
spanLinkNode.value.style.width = horizontalAnchor ? `${linkNode.clientWidth}px` : '';
if (horizontalAnchor) {
scrollIntoView(linkNode, {
scrollMode: 'if-needed',
block: 'nearest',
});
}
}
};

Expand All @@ -210,6 +245,7 @@ export default defineComponent({
handleClick: (e, info) => {
emit('click', e, info);
},
direction: anchorDirection,
});

onMounted(() => {
Expand Down Expand Up @@ -237,23 +273,31 @@ export default defineComponent({
}
updateInk();
});

watch([anchorDirection, getCurrentAnchor, state.links, activeLink], () => {
updateInk();
});

const createNestedLink = (options?: AnchorLinkItemProps[]) =>
Array.isArray(options)
? options.map(item => (
<AnchorLink {...item} key={item.key}>
{createNestedLink(item.children)}
{anchorDirection.value === 'vertical' ? createNestedLink(item.children) : null}
</AnchorLink>
))
: null;

const [wrapSSR, hashId] = useStyle(prefixCls);

return () => {
const { offsetTop, affix, showInkInFixed } = props;
const pre = prefixCls.value;
const inkClass = classNames(`${pre}-ink-ball`, {
[`${pre}-ink-ball-visible`]: activeLink.value,
const inkClass = classNames(`${pre}-ink`, {
[`${pre}-ink-visible`]: activeLink.value,
});

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

Expand All @@ -268,9 +312,7 @@ export default defineComponent({
const anchorContent = (
<div class={wrapperClass} style={wrapperStyle} ref={anchorRef}>
<div class={anchorClass}>
<div class={`${pre}-ink`}>
<span class={inkClass} ref={spanLinkNode} />
</div>
<span class={inkClass} ref={spanLinkNode} />
{Array.isArray(props.items) ? createNestedLink(props.items) : slots.default?.()}
</div>
</div>
Expand Down
82 changes: 51 additions & 31 deletions components/anchor/__tests__/__snapshots__/demo.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,28 @@

exports[`renders ./components/anchor/demo/basic.vue 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"></span></div>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
<!---->
<div class="css-dev-only-do-not-override-1tii49m">
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
<div class="ant-anchor"><span class="ant-anchor-ink"></span>
<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="#components-anchor-demo-static" title="Static demo">Static demo</a>
<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="#components-anchor-demo-basic" title="Basic demo with Target" target="_blank">Basic demo with Target</a>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#part-3" title="Part 3">Part 3</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/customizeHighlight.vue correctly 1`] = `
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
<div class="ant-anchor ant-anchor-fixed">
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
<div class="ant-anchor ant-anchor-fixed"><span class="ant-anchor-ink"></span>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
<!---->
</div>
Expand All @@ -51,10 +42,41 @@ exports[`renders ./components/anchor/demo/customizeHighlight.vue correctly 1`] =
</div>
`;

exports[`renders ./components/anchor/demo/horizontal.vue correctly 1`] = `
<div>
<div>
<!---->
<div class="css-dev-only-do-not-override-1tii49m">
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper ant-anchor-wrapper-horizontal" style="max-height: 100vh;">
<div class="ant-anchor"><span class="ant-anchor-ink"></span>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-1" title="Part 1">Part 1</a>
<!---->
</div>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-2" title="Part 2">Part 2</a>
<!---->
</div>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-3" title="Part 3">Part 3</a>
<!---->
</div>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-4" title="Part 4">Part 4</a>
<!---->
</div>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-5" title="Part 5">Part 5</a>
<!---->
</div>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#horizontally-part-6" title="Part 6">Part 6</a>
<!---->
</div>
</div>
</div>
</div>
</div>
</div>
`;

exports[`renders ./components/anchor/demo/onChange.vue correctly 1`] = `
<div class="ant-anchor-wrapper" style="max-height: 100vh;">
<div class="ant-anchor ant-anchor-fixed">
<div class="ant-anchor-ink"><span class="ant-anchor-ink-ball"></span></div>
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
<div class="ant-anchor ant-anchor-fixed"><span class="ant-anchor-ink"></span>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
<!---->
</div>
Expand All @@ -74,9 +96,8 @@ exports[`renders ./components/anchor/demo/onChange.vue correctly 1`] = `
`;

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

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

exports[`renders ./components/anchor/demo/targetOffset.vue 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"></span></div>
<!---->
<div class="css-dev-only-do-not-override-1tii49m">
<div class="css-dev-only-do-not-override-1tii49m ant-anchor-wrapper" style="max-height: 100vh;">
<div class="ant-anchor"><span class="ant-anchor-ink"></span>
<div class="ant-anchor-link"><a class="ant-anchor-link-title" href="#components-anchor-demo-basic" title="Basic demo">Basic demo</a>
<!---->
</div>
Expand Down
5 changes: 4 additions & 1 deletion components/anchor/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Ref, InjectionKey } from 'vue';
import type { Ref, InjectionKey, ComputedRef } from 'vue';
import type { AnchorDirection } from './Anchor';
import { computed, inject, provide } from 'vue';

export interface AnchorContext {
Expand All @@ -7,6 +8,7 @@ export interface AnchorContext {
activeLink: Ref<string>;
scrollTo: (link: string) => void;
handleClick: (e: Event, info: { title: any; href: string }) => void;
direction: ComputedRef<AnchorDirection>;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -25,6 +27,7 @@ const useInjectAnchor = () => {
scrollTo: noop,
activeLink: computed(() => ''),
handleClick: noop,
direction: computed(() => 'vertical'),
} as AnchorContext);
};

Expand Down
32 changes: 19 additions & 13 deletions components/anchor/demo/basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@ The simplest usage.
</docs>

<template>
<a-anchor>
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" />
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" />
<a-anchor-link
href="#components-anchor-demo-basic"
title="Basic demo with Target"
target="_blank"
/>
<a-anchor-link href="#API" title="API">
<a-anchor-link href="#Anchor-Props" title="Anchor Props" />
<a-anchor-link href="#Link-Props" title="Link Props" />
</a-anchor-link>
</a-anchor>
<a-anchor
: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',
},
]"
/>
</template>
Loading

0 comments on commit 8932aff

Please sign in to comment.