From 3a005178b20f77bdd1337faa800cf4dce6452138 Mon Sep 17 00:00:00 2001 From: CCherry07 <2405693142@qq.com> Date: Tue, 11 Apr 2023 17:29:10 +0800 Subject: [PATCH 1/7] refactor(anchor): direction show --- components/anchor/Anchor.tsx | 64 +++++++++++++++++++++++++++++------- components/anchor/context.ts | 5 ++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 4eeb4dc0f7..167ab5f38c 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -1,5 +1,6 @@ import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; import { + watch, defineComponent, nextTick, onBeforeUnmount, @@ -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'; @@ -20,6 +22,8 @@ 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; @@ -27,6 +31,9 @@ export interface AnchorLinkItemProps extends AnchorLinkProps { style?: CSSProperties; children?: AnchorLinkItemProps[]; } + +export type AnchorDirection = 'vertical' | 'horizontal'; + function getDefaultContainer() { return window; } @@ -73,6 +80,7 @@ export const anchorProps = () => ({ type: Array as PropType, 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>, }); @@ -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(null); const anchorRef = ref(); const state = reactive({ @@ -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', + }); + } } }; @@ -210,6 +245,7 @@ export default defineComponent({ handleClick: (e, info) => { emit('click', e, info); }, + direction: anchorDirection, }); onMounted(() => { @@ -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 => ( - {createNestedLink(item.children)} + {anchorDirection.value === 'vertical' ? createNestedLink(item.children) : null} )) : 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', }); @@ -268,9 +312,7 @@ export default defineComponent({ const anchorContent = (
-
- -
+ {Array.isArray(props.items) ? createNestedLink(props.items) : slots.default?.()}
diff --git a/components/anchor/context.ts b/components/anchor/context.ts index 55e79b3179..16c1c0812e 100644 --- a/components/anchor/context.ts +++ b/components/anchor/context.ts @@ -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 { @@ -7,6 +8,7 @@ export interface AnchorContext { activeLink: Ref; scrollTo: (link: string) => void; handleClick: (e: Event, info: { title: any; href: string }) => void; + direction: ComputedRef; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -25,6 +27,7 @@ const useInjectAnchor = () => { scrollTo: noop, activeLink: computed(() => ''), handleClick: noop, + direction: computed(() => 'vertical'), } as AnchorContext); }; From e274b748356028b58fc09761292f457ebd5bb988 Mon Sep 17 00:00:00 2001 From: CCherry07 <2405693142@qq.com> Date: Tue, 11 Apr 2023 17:30:28 +0800 Subject: [PATCH 2/7] refactor(anchor): update anchor css --- components/anchor/style/index.ts | 130 ++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 38 deletions(-) diff --git a/components/anchor/style/index.ts b/components/anchor/style/index.ts index 3a4039d978..119055aac8 100644 --- a/components/anchor/style/index.ts +++ b/components/anchor/style/index.ts @@ -16,8 +16,15 @@ interface AnchorToken extends FullToken<'Anchor'> { // ============================== Shared ============================== const genSharedAnchorStyle: GenerateStyle = (token): CSSObject => { - const { componentCls, holderOffsetBlock, motionDurationSlow, lineWidthBold, colorPrimary } = - token; + const { + componentCls, + holderOffsetBlock, + motionDurationSlow, + lineWidthBold, + colorPrimary, + lineType, + colorSplit, + } = token; return { [`${componentCls}-wrapper`]: { @@ -34,40 +41,6 @@ const genSharedAnchorStyle: GenerateStyle = (token): CSSObject => { 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`, @@ -96,13 +69,94 @@ const genSharedAnchorStyle: GenerateStyle = (token): CSSObject => { }, }, - [`${componentCls}-fixed ${componentCls}-ink ${componentCls}-ink-ball`]: { + [`&:not(${componentCls}-wrapper-horizontal)`]: { + [componentCls]: { + '&::before': { + position: 'absolute', + left: { + _skip_check_: true, + value: 0, + }, + top: 0, + height: '100%', + borderInlineStart: `${lineWidthBold}px ${lineType} ${colorSplit}`, + content: '" "', + }, + + [`${componentCls}-ink`]: { + 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-visible`]: { + display: 'inline-block', + }, + }, + }, + }, + + [`${componentCls}-fixed ${componentCls}-ink ${componentCls}-ink`]: { display: 'none', }, }, }; }; +const genSharedAnchorHorizontalStyle: GenerateStyle = (token): CSSObject => { + const { componentCls, motionDurationSlow, lineWidthBold, colorPrimary } = token; + + return { + [`${componentCls}-wrapper-horizontal`]: { + position: 'relative', + + '&::before': { + position: 'absolute', + left: { + _skip_check_: true, + value: 0, + }, + right: { + _skip_check_: true, + value: 0, + }, + bottom: 0, + borderBottom: `1px ${token.lineType} ${token.colorSplit}`, + content: '" "', + }, + + [componentCls]: { + overflowX: 'scroll', + position: 'relative', + display: 'flex', + scrollbarWidth: 'none' /* Firefox */, + + '&::-webkit-scrollbar': { + display: 'none' /* Safari and Chrome */, + }, + + [`${componentCls}-link:first-of-type`]: { + paddingInline: 0, + }, + + [`${componentCls}-ink`]: { + position: 'absolute', + bottom: 0, + transition: `left ${motionDurationSlow} ease-in-out, width ${motionDurationSlow} ease-in-out`, + height: lineWidthBold, + backgroundColor: colorPrimary, + }, + }, + }, + }; +}; + // ============================== Export ============================== export default genComponentStyleHook('Anchor', token => { const { fontSize, fontSizeLG, padding, paddingXXS } = token; @@ -115,5 +169,5 @@ export default genComponentStyleHook('Anchor', token => { anchorTitleBlock: (fontSize / 14) * 3, anchorBallSize: fontSizeLG / 2, }); - return [genSharedAnchorStyle(anchorToken)]; + return [genSharedAnchorStyle(anchorToken), genSharedAnchorHorizontalStyle(anchorToken)]; }); From 4a02bd69275079539e76f6de97d6a422790ce1f8 Mon Sep 17 00:00:00 2001 From: CCherry07 <2405693142@qq.com> Date: Tue, 11 Apr 2023 17:31:36 +0800 Subject: [PATCH 3/7] feat(anchor): update demo --- components/anchor/demo/basic.vue | 32 ++++++++------ components/anchor/demo/horizontal.vue | 64 +++++++++++++++++++++++++++ components/anchor/demo/index.vue | 3 ++ 3 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 components/anchor/demo/horizontal.vue diff --git a/components/anchor/demo/basic.vue b/components/anchor/demo/basic.vue index 6b480e0541..a21bf1196d 100644 --- a/components/anchor/demo/basic.vue +++ b/components/anchor/demo/basic.vue @@ -16,17 +16,23 @@ The simplest usage. diff --git a/components/anchor/demo/horizontal.vue b/components/anchor/demo/horizontal.vue new file mode 100644 index 0000000000..bdbedb3b9c --- /dev/null +++ b/components/anchor/demo/horizontal.vue @@ -0,0 +1,64 @@ + +--- +order: 1 +title: + zh-CN: 横向 Anchor。 + en-US: Horizontally aligned anchors +--- + + +## zh-CN + +横向 Anchor。 + +## en-US + +Horizontally aligned anchors + + + + diff --git a/components/anchor/demo/index.vue b/components/anchor/demo/index.vue index 0ba8272b90..f977562728 100644 --- a/components/anchor/demo/index.vue +++ b/components/anchor/demo/index.vue @@ -1,6 +1,7 @@