refactor: anchor & add items
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 ? (
|
||||
anchorContent
|
||||
) : (
|
||||
<Affix {...attrs} offsetTop={offsetTop} target={getContainer.value}>
|
||||
{anchorContent}
|
||||
</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) | | |
|
||||
| 事件名称 | 说明 | 回调参数 | 版本 | |
|
||||
| -------- | ---------------------- | ------------------------------------- | ---- | ----- |
|
||||
| change | 监听锚点链接改变 | (currentActiveLink: string) => void | | 1.5.0 |
|
||||
| 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…
Reference in New Issue