refactor: anchor & add items

pull/6213/head
tangjinzhou 2 years ago
parent f8ddc430cf
commit 6c735fee67

@ -16,7 +16,17 @@ import scrollTo from '../_util/scrollTo';
import getScroll from '../_util/getScroll';
import useConfigInject from '../_util/hooks/useConfigInject';
import useProvideAnchor from './context';
import useStyle from './style';
import type { AnchorLinkProps } from './AnchorLink';
import AnchorLink from './AnchorLink';
import type { Key } from '../_util/type';
export interface AnchorLinkItemProps extends AnchorLinkProps {
key: Key;
class?: String;
style?: CSSProperties;
children?: AnchorLinkItemProps[];
}
function getDefaultContainer() {
return window;
}
@ -41,10 +51,10 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
const sharpMatcherRegx = /#([\S ]+)$/;
type Section = {
interface Section {
link: string;
top: number;
};
}
export type AnchorContainer = HTMLElement | Window;
@ -59,6 +69,10 @@ export const anchorProps = () => ({
wrapperStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
getCurrentAnchor: Function as PropType<(activeLink: string) => string>,
targetOffset: Number,
items: {
type: Array as PropType<AnchorLinkItemProps[]>,
default: undefined as AnchorLinkItemProps[],
},
onChange: Function as PropType<(currentActiveLink: string) => void>,
onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>,
});
@ -79,7 +93,7 @@ export default defineComponent({
props: anchorProps(),
setup(props, { emit, attrs, slots, expose }) {
const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
const inkNodeRef = ref();
const spanLinkNode = ref<HTMLSpanElement>(null);
const anchorRef = ref();
const state = reactive<AnchorState>({
links: [],
@ -173,10 +187,9 @@ export default defineComponent({
const linkNode = anchorRef.value.getElementsByClassName(
`${prefixCls.value}-link-title-active`,
)[0];
if (linkNode) {
(inkNodeRef.value as HTMLElement).style.top = `${
linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5
}px`;
if (linkNode && spanLinkNode.value) {
spanLinkNode.value.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
spanLinkNode.value.style.height = `${linkNode.clientHeight}px`;
}
};
@ -224,15 +237,23 @@ export default defineComponent({
}
updateInk();
});
const createNestedLink = (options?: AnchorLinkItemProps[]) =>
Array.isArray(options)
? options.map(item => (
<AnchorLink {...item} key={item.key}>
{createNestedLink(item.children)}
</AnchorLink>
))
: null;
const [wrapSSR, hashId] = useStyle(prefixCls);
return () => {
const { offsetTop, affix, showInkInFixed } = props;
const pre = prefixCls.value;
const inkClass = classNames(`${pre}-ink-ball`, {
visible: activeLink.value,
[`${pre}-ink-ball-visible`]: activeLink.value,
});
const wrapperClass = classNames(props.wrapperClass, `${pre}-wrapper`, {
const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, {
[`${pre}-rtl`]: direction.value === 'rtl',
});
@ -248,19 +269,21 @@ export default defineComponent({
<div class={wrapperClass} style={wrapperStyle} ref={anchorRef}>
<div class={anchorClass}>
<div class={`${pre}-ink`}>
<span class={inkClass} ref={inkNodeRef} />
<span class={inkClass} ref={spanLinkNode} />
</div>
{slots.default?.()}
{Array.isArray(props.items) ? createNestedLink(props.items) : slots.default?.()}
</div>
</div>
);
return !affix ? (
return wrapSSR(
!affix ? (
anchorContent
) : (
<Affix {...attrs} offsetTop={offsetTop} target={getContainer.value}>
{anchorContent}
</Affix>
),
);
};
},

@ -24,17 +24,18 @@ For displaying anchor hyperlinks on page and jumping between them.
| getCurrentAnchor | Customize the anchor highlight | (activeLink: string) => string | - | activeLink(3.3) |
| offsetBottom | Pixels to offset from bottom when calculating position of scroll | number | - | |
| offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 | |
| showInkInFixed | Whether show ink-balls when `affix="false"` | boolean | false | |
| showInkInFixed | Whether show ink-square when `affix="false"` | boolean | false | |
| targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetOffset) | number | `offsetTop` | 1.5.0 |
| wrapperClass | The class name of the container | string | - | |
| wrapperStyle | The style of the container | object | - | |
| items | Data configuration option content, support nesting through children | { href, title, target, children, key }\[] | - | 4.0 |
### Events
| Events Name | Description | Arguments | Version | |
| --- | --- | --- | --- | --- |
| change | Listening for anchor link change | (currentActiveLink: string) => void | | 1.5.0 |
| click | set the handler to handle `click` event | Function(e: Event, link: Object) | | |
| click | set the handler to handle `click` event | Function(e: MouseEvent, link: Object) | | |
### Link Props

@ -25,17 +25,18 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_1-C1JwsC/Anchor.svg
| getCurrentAnchor | 自定义高亮的锚点 | (activeLink: string) => string | - | activeLink(3.3) |
| offsetBottom | 距离窗口底部达到指定偏移量后触发 | number | | |
| offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | | |
| showInkInFixed | `:affix="false"` 时是否显示小圆点 | boolean | false | |
| showInkInFixed | `:affix="false"` 时是否显示小方块 | boolean | false | |
| targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#components-anchor-demo-targetOffset) | number | `offsetTop` | 1.5.0 |
| wrapperClass | 容器的类名 | string | - | |
| wrapperStyle | 容器样式 | object | - | |
| items | 数据化配置选项内容,支持通过 children 嵌套 | { href, title, target, children, key }\[] | - | 4.0 |
### 事件
| 事件名称 | 说明 | 回调参数 | 版本 | |
| -------- | ---------------------- | ----------------------------------- | ---- | ----- |
| -------- | ---------------------- | ------------------------------------- | ---- | ----- |
| change | 监听锚点链接改变 | (currentActiveLink: string) => void | | 1.5.0 |
| click | `click` 事件的 handler | Function(e: Event, link: Object) | | |
| click | `click` 事件的 handler | Function(e: MouseEvent, link: Object) | | |
### Link Props

@ -1,86 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@anchor-border-width: 2px;
.@{ant-prefix}-anchor {
.reset-component();
position: relative;
padding-left: @anchor-border-width;
&-wrapper {
margin-left: -4px;
padding-left: 4px;
overflow: auto;
background-color: @anchor-bg;
}
&-ink {
position: absolute;
top: 0;
left: 0;
height: 100%;
&::before {
position: relative;
display: block;
width: @anchor-border-width;
height: 100%;
margin: 0 auto;
background-color: @anchor-border-color;
content: ' ';
}
&-ball {
position: absolute;
left: 50%;
display: none;
width: 8px;
height: 8px;
background-color: @component-background;
border: 2px solid @primary-color;
border-radius: 8px;
transform: translateX(-50%);
transition: top 0.3s ease-in-out;
&.visible {
display: inline-block;
}
}
}
&-fixed &-ink &-ink-ball {
display: none;
}
&-link {
padding: @anchor-link-padding;
&-title {
position: relative;
display: block;
margin-bottom: 3px;
overflow: hidden;
color: @text-color;
white-space: nowrap;
text-overflow: ellipsis;
transition: all 0.3s;
&:only-child {
margin-bottom: 0;
}
}
&-active > &-title {
color: @primary-color;
}
}
&-link &-link {
padding-top: 2px;
padding-bottom: 2px;
}
}
@import './rtl';

@ -0,0 +1,119 @@
import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent, textEllipsis } from '../../_style';
export interface ComponentToken {}
interface AnchorToken extends FullToken<'Anchor'> {
holderOffsetBlock: number;
anchorPaddingBlock: number;
anchorPaddingBlockSecondary: number;
anchorPaddingInline: number;
anchorBallSize: number;
anchorTitleBlock: number;
}
// ============================== Shared ==============================
const genSharedAnchorStyle: GenerateStyle<AnchorToken> = (token): CSSObject => {
const { componentCls, holderOffsetBlock, motionDurationSlow, lineWidthBold, colorPrimary } =
token;
return {
[`${componentCls}-wrapper`]: {
marginBlockStart: -holderOffsetBlock,
paddingBlockStart: holderOffsetBlock,
// delete overflow: auto
// overflow: 'auto',
backgroundColor: 'transparent',
[componentCls]: {
...resetComponent(token),
position: 'relative',
paddingInlineStart: lineWidthBold,
[`${componentCls}-ink`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
height: '100%',
'&::before': {
position: 'relative',
display: 'block',
width: lineWidthBold,
height: '100%',
margin: '0 auto',
backgroundColor: token.colorSplit,
content: '" "',
},
},
[`${componentCls}-ink-ball`]: {
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
display: 'none',
transform: 'translateY(-50%)',
transition: `top ${motionDurationSlow} ease-in-out`,
width: lineWidthBold,
backgroundColor: colorPrimary,
[`&${componentCls}-ink-ball-visible`]: {
display: 'inline-block',
},
},
[`${componentCls}-link`]: {
paddingBlock: token.anchorPaddingBlock,
paddingInline: `${token.anchorPaddingInline}px 0`,
'&-title': {
...textEllipsis,
position: 'relative',
display: 'block',
marginBlockEnd: token.anchorTitleBlock,
color: token.colorText,
transition: `all ${token.motionDurationSlow}`,
'&:only-child': {
marginBlockEnd: 0,
},
},
[`&-active > ${componentCls}-link-title`]: {
color: token.colorPrimary,
},
// link link
[`${componentCls}-link`]: {
paddingBlock: token.anchorPaddingBlockSecondary,
},
},
},
[`${componentCls}-fixed ${componentCls}-ink ${componentCls}-ink-ball`]: {
display: 'none',
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Anchor', token => {
const { fontSize, fontSizeLG, padding, paddingXXS } = token;
const anchorToken = mergeToken<AnchorToken>(token, {
holderOffsetBlock: paddingXXS,
anchorPaddingBlock: paddingXXS,
anchorPaddingBlockSecondary: paddingXXS / 2,
anchorPaddingInline: padding,
anchorTitleBlock: (fontSize / 14) * 3,
anchorBallSize: fontSizeLG / 2,
});
return [genSharedAnchorStyle(anchorToken)];
});

@ -1,5 +0,0 @@
import '../../style/index.less';
import './index.less';
// style dependencies
import '../../affix/style';

@ -1,35 +0,0 @@
.@{ant-prefix}-anchor {
&-rtl {
direction: rtl;
}
&-wrapper {
.@{ant-prefix}-anchor-rtl& {
margin-right: -4px;
margin-left: 0;
padding-right: 4px;
padding-left: 0;
}
}
&-ink {
.@{ant-prefix}-anchor-rtl & {
right: 0;
left: auto;
}
&-ball {
.@{ant-prefix}-anchor-rtl & {
right: 50%;
left: 0;
transform: translateX(50%);
}
}
}
&-link {
.@{ant-prefix}-anchor-rtl & {
padding: @anchor-link-top @anchor-link-left @anchor-link-top 0;
}
}
}

@ -45,7 +45,7 @@ import './transfer/style';
import './tree/style';
import './upload/style';
import './layout/style';
import './anchor/style';
// import './anchor/style';
import './list/style';
import './tree-select/style';
import './drawer/style';

@ -1,5 +1,5 @@
import type { ComponentToken as AlertComponentToken } from '../../alert/style';
// import type { ComponentToken as AnchorComponentToken } from '../../anchor/style';
import type { ComponentToken as AnchorComponentToken } from '../../anchor/style';
import type { ComponentToken as AvatarComponentToken } from '../../avatar/style';
// import type { ComponentToken as BackTopComponentToken } from '../../back-top/style';
// import type { ComponentToken as ButtonComponentToken } from '../../button/style';
@ -53,7 +53,7 @@ import type { ComponentToken as AvatarComponentToken } from '../../avatar/style'
export interface ComponentTokenMap {
Affix?: {};
Alert?: AlertComponentToken;
// Anchor?: AnchorComponentToken;
Anchor?: AnchorComponentToken;
Avatar?: AvatarComponentToken;
// BackTop?: BackTopComponentToken;
// Badge?: {};

Loading…
Cancel
Save