feat: anchor add customTitle slot #6447

pull/6514/head
tangjinzhou 2023-04-21 21:29:20 +08:00
parent 8932aff13f
commit bccd9204e8
11 changed files with 238 additions and 83 deletions

View File

@ -1,6 +1,5 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import { import {
watch,
defineComponent, defineComponent,
nextTick, nextTick,
onBeforeUnmount, onBeforeUnmount,
@ -19,18 +18,11 @@ import getScroll from '../_util/getScroll';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import useProvideAnchor from './context'; import useProvideAnchor from './context';
import useStyle from './style'; import useStyle from './style';
import type { AnchorLinkProps } from './AnchorLink'; import type { AnchorLinkItemProps } from './AnchorLink';
import AnchorLink from './AnchorLink'; import AnchorLink from './AnchorLink';
import type { Key } from '../_util/type';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import devWarning from '../vc-util/devWarning'; import devWarning from '../vc-util/devWarning';
import { arrayType } from '../_util/type';
export interface AnchorLinkItemProps extends AnchorLinkProps {
key: Key;
class?: String;
style?: CSSProperties;
children?: AnchorLinkItemProps[];
}
export type AnchorDirection = 'vertical' | 'horizontal'; export type AnchorDirection = 'vertical' | 'horizontal';
@ -76,10 +68,7 @@ export const anchorProps = () => ({
wrapperStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, wrapperStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
getCurrentAnchor: Function as PropType<(activeLink: string) => string>, getCurrentAnchor: Function as PropType<(activeLink: string) => string>,
targetOffset: Number, targetOffset: Number,
items: { items: arrayType<AnchorLinkItemProps[]>(),
type: Array as PropType<AnchorLinkItemProps[]>,
default: undefined as AnchorLinkItemProps[],
},
direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'), direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'),
onChange: Function as PropType<(currentActiveLink: string) => void>, onChange: Function as PropType<(currentActiveLink: string) => void>,
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>, onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
@ -105,7 +94,7 @@ export default defineComponent({
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
devWarning( devWarning(
typeof slots.default !== 'function', props.items && typeof slots.default !== 'function',
'Anchor', 'Anchor',
'`Anchor children` is deprecated. Please use `items` instead.', '`Anchor children` is deprecated. Please use `items` instead.',
); );
@ -274,17 +263,25 @@ export default defineComponent({
updateInk(); updateInk();
}); });
watch([anchorDirection, getCurrentAnchor, state.links, activeLink], () => {
updateInk();
});
const createNestedLink = (options?: AnchorLinkItemProps[]) => const createNestedLink = (options?: AnchorLinkItemProps[]) =>
Array.isArray(options) Array.isArray(options)
? options.map(item => ( ? options.map(option => {
<AnchorLink {...item} key={item.key}> const { children, key, href, target, class: cls, style, title } = option;
{anchorDirection.value === 'vertical' ? createNestedLink(item.children) : null} return (
<AnchorLink
key={key}
href={href}
target={target}
class={cls}
style={style}
title={title}
customTitleProps={option}
v-slots={{ customTitle: slots.customTitle }}
>
{anchorDirection.value === 'vertical' ? createNestedLink(children) : null}
</AnchorLink> </AnchorLink>
)) );
})
: null; : null;
const [wrapSSR, hashId] = useStyle(prefixCls); const [wrapSSR, hashId] = useStyle(prefixCls);

View File

@ -1,26 +1,40 @@
import type { ExtractPropTypes } from 'vue'; import type { ExtractPropTypes } from 'vue';
import { defineComponent, nextTick, onBeforeUnmount, onMounted, watch } from 'vue'; import { defineComponent, nextTick, onBeforeUnmount, onMounted, watch } from 'vue';
import PropTypes from '../_util/vue-types'; import { initDefaultProps } from '../_util/props-util';
import { getPropsSlot, initDefaultProps } from '../_util/props-util';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import useConfigInject from '../config-provider/hooks/useConfigInject'; import useConfigInject from '../config-provider/hooks/useConfigInject';
import { useInjectAnchor } from './context'; import { useInjectAnchor } from './context';
import type { Key, VueNode } from '../_util/type';
import { objectType, anyType } from '../_util/type';
import type { CSSProperties } from '../_util/cssinjs/hooks/useStyleRegister';
export const anchorLinkProps = () => ({ export const anchorLinkProps = () => ({
prefixCls: String, prefixCls: String,
href: String, href: String,
title: PropTypes.any, title: anyType<VueNode | ((item: any) => VueNode)>(),
target: String, target: String,
/* private use */
customTitleProps: objectType<AnchorLinkItemProps>(),
}); });
export interface AnchorLinkItemProps {
key: Key;
class?: string;
style?: CSSProperties;
href?: string;
target?: string;
children?: AnchorLinkItemProps[];
title?: VueNode | ((item: AnchorLinkItemProps) => VueNode);
}
export type AnchorLinkProps = Partial<ExtractPropTypes<ReturnType<typeof anchorLinkProps>>>; export type AnchorLinkProps = Partial<ExtractPropTypes<ReturnType<typeof anchorLinkProps>>>;
export default defineComponent({ export default defineComponent({
compatConfig: { MODE: 3 }, compatConfig: { MODE: 3 },
name: 'AAnchorLink', name: 'AAnchorLink',
inheritAttrs: false,
props: initDefaultProps(anchorLinkProps(), { href: '#' }), props: initDefaultProps(anchorLinkProps(), { href: '#' }),
slots: ['title'], slots: ['title', 'customTitle'],
setup(props, { slots }) { setup(props, { slots, attrs }) {
let mergedTitle = null; let mergedTitle = null;
const { const {
handleClick: contextHandleClick, handleClick: contextHandleClick,
@ -56,27 +70,30 @@ export default defineComponent({
}); });
return () => { return () => {
const { href, target } = props; const { href, target, title = slots.title, customTitleProps = {} } = props;
const pre = prefixCls.value; const pre = prefixCls.value;
const title = getPropsSlot(slots, props, 'title'); mergedTitle = typeof title === 'function' ? title(customTitleProps) : title;
mergedTitle = title;
const active = activeLink.value === href; const active = activeLink.value === href;
const wrapperClassName = classNames(`${pre}-link`, { const wrapperClassName = classNames(
`${pre}-link`,
{
[`${pre}-link-active`]: active, [`${pre}-link-active`]: active,
}); },
attrs.class,
);
const titleClassName = classNames(`${pre}-link-title`, { const titleClassName = classNames(`${pre}-link-title`, {
[`${pre}-link-title-active`]: active, [`${pre}-link-title-active`]: active,
}); });
return ( return (
<div class={wrapperClassName}> <div {...attrs} class={wrapperClassName}>
<a <a
class={titleClassName} class={titleClassName}
href={href} href={href}
title={typeof title === 'string' ? title : ''} title={typeof mergedTitle === 'string' ? mergedTitle : ''}
target={target} target={target}
onClick={handleClick} onClick={handleClick}
> >
{title} {slots.customTitle ? slots.customTitle(customTitleProps) : mergedTitle}
</a> </a>
{slots.default?.()} {slots.default?.()}
</div> </div>

View File

@ -21,7 +21,7 @@ The simplest usage.
{ {
key: 'part-1', key: 'part-1',
href: '#part-1', href: '#part-1',
title: 'Part 1', title: () => h('span', { style: 'color: red' }, 'Part 1'),
}, },
{ {
key: 'part-2', key: 'part-2',
@ -36,3 +36,19 @@ The simplest usage.
]" ]"
/> />
</template> </template>
<script lang="ts">
import { defineComponent, h } from 'vue';
export default defineComponent({
setup() {
const onChange = (link: string) => {
console.log('Anchor:OnChange', link);
};
return {
onChange,
h,
};
},
});
</script>

View File

@ -16,14 +16,39 @@ Customize the anchor highlight.
</docs> </docs>
<template> <template>
<a-anchor :affix="false" :get-current-anchor="getCurrentAnchor"> <a-anchor
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" /> :affix="false"
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" /> :get-current-anchor="getCurrentAnchor"
<a-anchor-link href="#API" title="API"> :items="[
<a-anchor-link href="#Anchor-Props" title="Anchor Props" /> {
<a-anchor-link href="#Link-Props" title="Link Props" /> key: '1',
</a-anchor-link> href: '#components-anchor-demo-basic',
</a-anchor> 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',
},
],
},
]"
></a-anchor>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -16,14 +16,39 @@ Listening for anchor link change.
</docs> </docs>
<template> <template>
<a-anchor :affix="false" @change="onChange"> <a-anchor
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" /> :affix="false"
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" /> :items="[
<a-anchor-link href="#API" title="API"> {
<a-anchor-link href="#Anchor-Props" title="Anchor Props" /> key: '1',
<a-anchor-link href="#Link-Props" title="Link Props" /> href: '#components-anchor-demo-basic',
</a-anchor-link> title: 'Basic demo',
</a-anchor> },
{
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',
},
],
},
]"
@change="onChange"
></a-anchor>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -16,14 +16,39 @@ Clicking on an anchor does not record history.
</docs> </docs>
<template> <template>
<a-anchor :affix="false" @click="handleClick"> <a-anchor
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" /> :affix="false"
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" /> :items="[
<a-anchor-link href="#API" title="API"> {
<a-anchor-link href="#Anchor-Props" title="Anchor Props" /> key: '1',
<a-anchor-link href="#Link-Props" title="Link Props" /> href: '#components-anchor-demo-basic',
</a-anchor-link> title: 'Basic demo',
</a-anchor> },
{
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',
},
],
},
]"
@click="handleClick"
></a-anchor>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -16,12 +16,36 @@ Do not change state when page is scrolling.
</docs> </docs>
<template> <template>
<a-anchor :affix="false"> <a-anchor
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" /> :affix="false"
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" /> :items="[
<a-anchor-link href="#API" title="API"> {
<a-anchor-link href="#Anchor-Props" title="Anchor Props" /> key: '1',
<a-anchor-link href="#Link-Props" title="Link Props" /> href: '#components-anchor-demo-basic',
</a-anchor-link> title: 'Basic demo',
</a-anchor> },
{
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',
},
],
},
]"
></a-anchor>
</template> </template>

View File

@ -16,14 +16,38 @@ Anchor target scroll to screen center.
</docs> </docs>
<template> <template>
<a-anchor :target-offset="targetOffset"> <a-anchor
<a-anchor-link href="#components-anchor-demo-basic" title="Basic demo" /> :target-offset="targetOffset"
<a-anchor-link href="#components-anchor-demo-static" title="Static demo" /> :items="[
<a-anchor-link href="#API" title="API"> {
<a-anchor-link href="#Anchor-Props" title="Anchor Props" /> key: '1',
<a-anchor-link href="#Link-Props" title="Link Props" /> href: '#components-anchor-demo-basic',
</a-anchor-link> title: 'Basic demo',
</a-anchor> },
{
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',
},
],
},
]"
></a-anchor>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -1,7 +1,7 @@
--- ---
category: Components category: Components
type: Navigation type: Navigation
cols: 2 cols: 1
title: Anchor title: Anchor
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JGb3RIzyOCkAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JGb3RIzyOCkAAAAAAAAAAAAADrJ8AQ/original
@ -31,6 +31,7 @@ For displaying anchor hyperlinks on page and jumping between them.
| wrapperStyle | The style of the container | object | - | | | wrapperStyle | The style of the container | object | - | |
| items | Data configuration option content, support nesting through children | { key, href, title, target, children }\[] [see](#anchoritem) | - | 4.0 | | items | Data configuration option content, support nesting through children | { key, href, title, target, children }\[] [see](#anchoritem) | - | 4.0 |
| direction | Set Anchor direction | `vertical` \| `horizontal` | `vertical` | 4.0 | | direction | Set Anchor direction | `vertical` \| `horizontal` | `vertical` | 4.0 |
| customTitle | custom option title by slot | v-slot="AnchorItem" | - | 4.0 |
### AnchorItem ### AnchorItem

View File

@ -1,6 +1,6 @@
import type { App, Plugin } from 'vue'; import type { App, Plugin } from 'vue';
import type { AnchorProps } from './Anchor'; import type { AnchorProps } from './Anchor';
import type { AnchorLinkProps } from './AnchorLink'; import type { AnchorLinkProps, AnchorLinkItemProps } from './AnchorLink';
import Anchor from './Anchor'; import Anchor from './Anchor';
import AnchorLink from './AnchorLink'; import AnchorLink from './AnchorLink';
@ -13,7 +13,7 @@ Anchor.install = function (app: App) {
return app; return app;
}; };
export type { AnchorLinkProps, AnchorProps }; export type { AnchorLinkProps, AnchorProps, AnchorLinkItemProps };
export { AnchorLink, AnchorLink as Link }; export { AnchorLink, AnchorLink as Link };
export default Anchor as typeof Anchor & export default Anchor as typeof Anchor &

View File

@ -1,7 +1,7 @@
--- ---
category: Components category: Components
subtitle: 锚点 subtitle: 锚点
cols: 2 cols: 1
type: 导航 type: 导航
title: Anchor title: Anchor
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
@ -32,6 +32,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JGb3RIzyOCkAAA
| wrapperStyle | 容器样式 | object | - | | | wrapperStyle | 容器样式 | object | - | |
| items | 数据化配置选项内容,支持通过 children 嵌套 | { key, href, title, target, children }\[] [具体见](#anchoritem) | - | 4.0 | | items | 数据化配置选项内容,支持通过 children 嵌套 | { key, href, title, target, children }\[] [具体见](#anchoritem) | - | 4.0 |
| direction | 设置导航方向 | `vertical` \| `horizontal` | `vertical` | 4.0 | | direction | 设置导航方向 | `vertical` \| `horizontal` | `vertical` | 4.0 |
| customTitle | 使用插槽自定义选项 title | v-slot="AnchorItem" | - | 4.0 |
### AnchorItem ### AnchorItem