feat(anchor): showRail, showBackground

This commit is contained in:
07akioni 2021-02-27 13:35:59 +08:00
parent bfd1c84852
commit 97ece8c8d6
9 changed files with 183 additions and 173 deletions

View File

@ -10,48 +10,52 @@ import {
} from 'vue'
import { getScrollParent, unwrapElement } from 'seemly'
import { useStyle } from '../../_mixins'
import { warn } from '../../_utils'
import { warn, keysOf } from '../../_utils'
import style from './styles/index.cssr'
export const affixProps = {
listenTo: {
type: [String, Object] as PropType<
string | (() => HTMLElement) | undefined
>,
default: undefined
},
offsetTop: {
type: Number,
default: undefined
},
top: {
type: Number,
default: undefined
},
offsetBottom: {
type: Number,
default: undefined
},
bottom: {
type: Number,
default: undefined
},
position: {
type: String,
default: 'fix'
},
// deprecated
target: {
type: Function as PropType<(() => HTMLElement) | undefined>,
validator: () => {
warn('affix', '`target` is deprecated, please use `listen-to` instead.')
return true
},
default: undefined
}
} as const
export const affixPropKeys = keysOf(affixProps)
export default defineComponent({
name: 'Affix',
props: {
listenTo: {
type: [String, Object] as PropType<
string | (() => HTMLElement) | undefined
>,
default: undefined
},
offsetTop: {
type: Number,
default: undefined
},
top: {
type: Number,
default: undefined
},
offsetBottom: {
type: Number,
default: undefined
},
bottom: {
type: Number,
default: undefined
},
position: {
type: String,
default: 'fix'
},
// deprecated
target: {
type: Function as PropType<(() => HTMLElement) | undefined>,
validator: () => {
warn('affix', '`target` is deprecated, please use `listen-to` instead.')
return true
},
default: undefined
}
},
props: affixProps,
setup (props) {
useStyle('Affix', style)
const scrollElementRef = ref<HTMLElement | null>(null)

View File

@ -1,15 +1,30 @@
# Basic
```html
<div>
<n-anchor>
<n-anchor-link title="Demos" href="#Demos">
<n-anchor-link title="Basic" href="#basic" />
<n-anchor-link title="Ignore-Gap" href="#ignore-gap" />
<n-anchor-link title="Affix" href="#affix" />
<n-anchor-link title="Scroll To" href="#scrollto" />
</n-anchor-link>
<n-anchor-link title="Props" href="#Props" />
</n-anchor>
</div>
<n-space style="margin-bottom: 12px;">
<n-switch v-model:value="showRail" /> Show Rail
<n-switch v-model:value="showBackground" /> Show Background
</n-space>
<n-anchor>
<n-anchor-link title="Demos" href="#Demos">
<n-anchor-link title="Basic" href="#basic" />
<n-anchor-link title="Ignore-Gap" href="#ignore-gap" />
<n-anchor-link title="Affix" href="#affix" />
<n-anchor-link title="Scroll To" href="#scrollto" />
</n-anchor-link>
<n-anchor-link title="Props" href="#Props" />
</n-anchor>
```
```js
import { ref } from 'vue'
export default {
setup () {
return {
showRail: ref(true),
showBackground: ref(true)
}
}
}
```

View File

@ -11,7 +11,6 @@ basic
ignore-gap
affix
scrollto
```
## Props
@ -22,6 +21,8 @@ scrollto
| bound | `number` | `12` | |
| ignore-gap | `boolean` | `false` | If set to `true`, it will be displayed on the exact href |
| listen-to | `string \| HTMLElement` | `undefined` | The scrolling element to listen scrolling. If not set it will listen to the nearest scrollable ascendant element. |
| show-rail | `boolean` | `true` | Whether to show the sider rail. |
| show-background | `boolean` | `true` | Whether to show background of links. |
## Methods

View File

@ -1,15 +1,30 @@
# 基础用法
```html
<div>
<n-anchor>
<n-anchor-link title="演示" href="#演示">
<n-anchor-link title="基础用法" href="#basic" />
<n-anchor-link title="忽略间隔" href="#ignore-gap" />
<n-anchor-link title="固定" href="#affix" />
<n-anchor-link title="滚动到" href="#scrollto" />
</n-anchor-link>
<n-anchor-link title="Props" href="#Props" />
</n-anchor>
</div>
<n-space style="margin-bottom: 12px;">
<n-switch v-model:value="showRail" /> 展示轨道
<n-switch v-model:value="showBackground" /> 展示背景
</n-space>
<n-anchor :show-rail="showRail" :show-background="showBackground">
<n-anchor-link title="演示" href="#演示">
<n-anchor-link title="基础用法" href="#basic" />
<n-anchor-link title="忽略间隔" href="#ignore-gap" />
<n-anchor-link title="固定" href="#affix" />
<n-anchor-link title="滚动到" href="#scrollto" />
</n-anchor-link>
<n-anchor-link title="Props" href="#Props" />
</n-anchor>
```
```js
import { ref } from 'vue'
export default {
setup () {
return {
showRail: ref(true),
showBackground: ref(true)
}
}
}
```

View File

@ -21,6 +21,8 @@ scrollto
| bound | `number` | `12` | |
| ignore-gap | `boolean` | `false` | 如果设定为 `true`, 导航将显示在准确的 href 区域 |
| listen-to | `string \| HTMLElement` | `undefined` | 需要监听滚动的元素,如果未设定则会监听最近的可滚动祖先元素 |
| show-rail | `boolean` | `true` | 是否展示侧面的轨道 |
| show-background | `boolean` | `true` | 是否展示 link 的背景 |
## Methods

View File

@ -1,11 +1,13 @@
import { h, defineComponent, computed, ref, CSSProperties, PropType } from 'vue'
import { h, defineComponent, computed, ref, CSSProperties } from 'vue'
import { NAffix } from '../../affix'
import { affixProps, affixPropKeys } from '../../affix/src/Affix'
import { useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { keep } from '../../_utils'
import { anchorLight } from '../styles'
import type { AnchorTheme } from '../styles'
import style from './styles/index.cssr'
import NBaseAnchor from './BaseAnchor'
import NBaseAnchor, { baseAnchorProps, baseAnchorPropKeys } from './BaseAnchor'
import type { BaseAnchorRef } from './BaseAnchor'
export interface AnchorRef {
@ -16,52 +18,12 @@ export default defineComponent({
name: 'Anchor',
props: {
...(useTheme.props as ThemeProps<AnchorTheme>),
top: {
type: Number,
default: undefined
},
affix: {
type: Boolean,
default: false
},
position: {
type: String,
default: undefined
},
bottom: {
type: Number,
default: undefined
},
offsetBottom: {
type: Number,
default: undefined
},
offsetTop: {
type: Number,
default: undefined
},
bound: {
type: Number,
default: 12
},
ignoreGap: {
type: Boolean,
default: false
},
listenTo: {
type: [String, Object] as PropType<
string | (() => HTMLElement) | undefined
>,
default: undefined
},
// deprecated
target: {
type: Function as PropType<(() => HTMLElement) | undefined>,
validator: () => {
return true
},
default: undefined
}
...affixProps,
...baseAnchorProps
},
setup (props) {
const themeRef = useTheme('Anchor', 'Anchor', style, anchorLight, props)
@ -105,10 +67,7 @@ export default defineComponent({
<NBaseAnchor
ref="anchorRef"
style={this.cssVars as CSSProperties}
listenTo={this.listenTo}
bound={this.bound}
target={this.target}
ignoreGap={this.ignoreGap}
{...keep(this, baseAnchorPropKeys)}
>
{this.$slots}
</NBaseAnchor>
@ -116,15 +75,7 @@ export default defineComponent({
return !this.affix ? (
anchorNode
) : (
<NAffix
listenTo={this.listenTo}
top={this.top}
bottom={this.bottom}
offsetTop={this.offsetTop}
offsetBottom={this.offsetBottom}
position={this.position}
target={this.target}
>
<NAffix {...keep(this, affixPropKeys)}>
{{ default: () => anchorNode }}
</NAffix>
)

View File

@ -14,7 +14,7 @@ import {
} from 'vue'
import { getScrollParent, unwrapElement } from 'seemly'
import { onFontsReady } from 'vooks'
import { warn } from '../../_utils'
import { warn, keysOf } from '../../_utils'
import type { AnchorInjection } from './Link'
export interface BaseAnchorRef {
@ -36,33 +36,42 @@ function getOffset (
}
}
export const baseAnchorProps = {
listenTo: [String, Object] as PropType<string | (() => HTMLElement)>,
showRail: {
type: Boolean,
default: true
},
showBackground: {
type: Boolean,
default: true
},
bound: {
type: Number,
default: 12
},
ignoreGap: {
type: Boolean,
default: false
},
// deprecated
target: {
type: Function as PropType<(() => HTMLElement) | undefined>,
validator: () => {
if (__DEV__) {
warn('anchor', '`target` is deprecated, please use`listen-to` instead.')
}
return true
},
default: undefined
}
} as const
export const baseAnchorPropKeys = keysOf(baseAnchorProps)
export default defineComponent({
name: 'BaseAnchor',
props: {
listenTo: [String, Object] as PropType<string | (() => HTMLElement)>,
bound: {
type: Number,
default: 12
},
ignoreGap: {
type: Boolean,
default: false
},
// deprecated
target: {
type: Function as PropType<(() => HTMLElement) | undefined>,
validator: () => {
if (__DEV__) {
warn(
'anchor',
'`target` is deprecated, please use`listen-to` instead.'
)
}
return true
},
default: undefined
}
},
props: baseAnchorProps,
setup (props) {
let scrollElement: HTMLElement | null
const collectedLinkHrefs: string[] = markRaw([])
@ -111,10 +120,10 @@ export default defineComponent({
const { value: barEl } = barRef
const { value: slotEl } = slotRef
const { value: selfEl } = selfRef
if (!selfEl || !barEl || !slotEl) return
if (!selfEl || !barEl) return
if (!transition) {
barEl.style.transition = 'none'
slotEl.style.transition = 'none'
if (slotEl) slotEl.style.transition = 'none'
}
const { offsetHeight, offsetWidth } = linkTitleEl
const {
@ -129,15 +138,17 @@ export default defineComponent({
const offsetLeft = linkTitleClientLeft - anchorClientLeft
barEl.style.top = `${offsetTop}px`
barEl.style.height = `${offsetHeight}px`
slotEl.style.top = `${offsetTop}px`
slotEl.style.height = `${offsetHeight}px`
slotEl.style.maxWidth = `${offsetWidth + offsetLeft}px`
if (slotEl) {
slotEl.style.top = `${offsetTop}px`
slotEl.style.height = `${offsetHeight}px`
slotEl.style.maxWidth = `${offsetWidth + offsetLeft}px`
}
void barEl.offsetHeight
void slotEl.offsetHeight
if (slotEl) void slotEl.offsetHeight
if (!transition) {
barEl.style.transition = ''
slotEl.style.transition = ''
if (slotEl) slotEl.style.transition = ''
}
}
function setActiveHref (href: string, transition = true): void {
@ -286,19 +297,26 @@ export default defineComponent({
},
render () {
return (
<div class="n-anchor" ref="selfRef">
<div ref="slotRef" class="n-anchor-link-background" />
<div class="n-anchor-rail">
<div
ref="barRef"
class={[
'n-anchor-rail__bar',
{
'n-anchor-rail__bar--active': this.activeHref !== null
}
]}
/>
</div>
<div
class={['n-anchor', this.showRail && 'n-anchor--show-rail']}
ref="selfRef"
>
{this.showRail && this.showBackground ? (
<div ref="slotRef" class="n-anchor-link-background" />
) : null}
{this.showRail ? (
<div class="n-anchor-rail">
<div
ref="barRef"
class={[
'n-anchor-rail__bar',
{
'n-anchor-rail__bar--active': this.activeHref !== null
}
]}
/>
</div>
) : null}
{renderSlot(this.$slots, 'default')}
</div>
)

View File

@ -1,4 +1,4 @@
import { c, cE, cB, cM } from '../../../_utils/cssr'
import { c, cE, cB, cM, cNotM } from '../../../_utils/cssr'
// vars:
// --link-color
@ -51,15 +51,16 @@ export default cB('anchor', `
cM('active', {
backgroundColor: 'var(--rail-color-active)'
})
]),
c('+', [
])
]),
cNotM('show-rail', [
c('>', [
cB('anchor-link', {
marginTop: 0
paddingLeft: 0
})
])
]),
cB('anchor-link', `
margin-top: .5em;
padding-left: 16px;
position: relative;
line-height: 1.5;
@ -68,10 +69,10 @@ export default cB('anchor', `
display: flex;
flex-direction: column;
`, [
c('+', [
cB('anchor-link', {
paddingLeft: '16px'
})
c('+, >', [
cB('anchor-link', `
margin-top: .5em;
`)
]),
cE('title', `
outline: none;

View File

@ -22,6 +22,9 @@
- `target` => `listen-to`
- [x] alert
- [x] anchor
- new
- `show-rail` props
- `show-background` props
- deprecate
- `target` => `listen-to`
- [x] auto-complete