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
	
	 tangjinzhou
						tangjinzhou