完善tabs组件
parent
341f096b76
commit
b0196b23d9
|
@ -4,7 +4,15 @@
|
|||
* @date 2023/03/06 15:17:25
|
||||
*/
|
||||
|
||||
import { css, html, bind, Component, nextTick } from '@bd/core'
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
bind,
|
||||
Component,
|
||||
nextTick,
|
||||
styleMap,
|
||||
classMap
|
||||
} from '@bd/core'
|
||||
|
||||
class Tabs extends Component {
|
||||
static props = {
|
||||
|
@ -18,51 +26,263 @@ class Tabs extends Component {
|
|||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:host([tab-position='top']),
|
||||
:host([tab-position='bottom']) {
|
||||
:host([tab-position='top']) {
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([tab-position='left']),
|
||||
:host([tab-position='bottom']) {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
:host([tab-position='right']) {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.ell {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
color: var(--color-dark-1);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 120px;
|
||||
margin: 0 16px;
|
||||
--size: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
[slot='label'] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
color: var(--color-teal-1);
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
// type default
|
||||
css`
|
||||
:host([type='default']) {
|
||||
.header {
|
||||
user-select: none;
|
||||
border-bottom: 2px solid var(--color-plain-3);
|
||||
}
|
||||
|
||||
.active-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -2px;
|
||||
height: 2px;
|
||||
background: var(--color-teal-1);
|
||||
}
|
||||
.active-bar {
|
||||
transition: width 0.2s ease-out, left 0.2s ease-out, top 0.2s ease-out;
|
||||
}
|
||||
|
||||
&:host([tab-position='bottom']) {
|
||||
.header {
|
||||
border-top: 2px solid var(--color-plain-3);
|
||||
border-bottom: 0;
|
||||
}
|
||||
.active-bar {
|
||||
top: -2px;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:host([tab-position='left']),
|
||||
&:host([tab-position='right']) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 120px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
.label {
|
||||
margin: 0;
|
||||
height: 38px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.active-bar {
|
||||
width: 2px;
|
||||
height: 38px;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
&:host([tab-position='left']) {
|
||||
.header {
|
||||
border-right: 2px solid var(--color-plain-3);
|
||||
}
|
||||
.label {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.active-bar {
|
||||
left: unset;
|
||||
right: -2px;
|
||||
}
|
||||
}
|
||||
&:host([tab-position='right']) {
|
||||
.header {
|
||||
border-left: 2px solid var(--color-plain-3);
|
||||
}
|
||||
|
||||
.active-bar {
|
||||
left: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
// type folder && card
|
||||
css`
|
||||
:host([type='folder']),
|
||||
:host([type='card']) {
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.active-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
background: var(--color-plain-1);
|
||||
}
|
||||
}
|
||||
|
||||
:host([type='folder']) {
|
||||
.header {
|
||||
overflow: hidden;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--tab-folder-color, #fff);
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-top: 2px;
|
||||
background: none;
|
||||
|
||||
> span,
|
||||
> [slot='label'] {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
background: var(--color-plain-3);
|
||||
border-radius: 6px 6px 0 0;
|
||||
transform: perspective(9px) rotateX(3deg);
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1);
|
||||
content: '';
|
||||
}
|
||||
|
||||
&.active::before {
|
||||
z-index: 0;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:host([tab-position='bottom']) {
|
||||
.label {
|
||||
padding-top: 0;
|
||||
|
||||
&::before {
|
||||
border-radius: 0 0 6px 6px;
|
||||
transform: perspective(9px) rotateX(-3deg);
|
||||
box-shadow: 0 -1px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host([type='card']) {
|
||||
border: 1px solid var(--color-plain-3);
|
||||
background: #fff;
|
||||
|
||||
.header {
|
||||
background: var(--color-plain-1);
|
||||
box-shadow: inset 0 -1px 0 0px var(--color-plain-3);
|
||||
}
|
||||
.label {
|
||||
border-right: 1px solid var(--color-plain-3);
|
||||
border-bottom: 1px solid var(--color-plain-3);
|
||||
|
||||
&.active {
|
||||
border-bottom-color: transparent;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
]
|
||||
|
||||
#cache = {}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="tabs">
|
||||
<header class="header" ref="tabs" @click=${this.selectTab}>
|
||||
${this.labels.map(
|
||||
(it, i) =>
|
||||
html`<label class="label" data-key=${it.name}>${it.label}</label>`
|
||||
)}
|
||||
</header>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
#tabWidth = 0 // 选项卡的宽度
|
||||
#tabLeft = 0 // 选项卡的左侧距离
|
||||
#tabTop = 0 // 选项卡的顶部距离
|
||||
|
||||
// 处理子组件的slot,穿透到父组件来
|
||||
#parseSlot() {
|
||||
let left = 0
|
||||
let gaps = this.type === 'default' ? 32 : 0
|
||||
let top = 0
|
||||
this.labels.forEach((it, i) => {
|
||||
let tab = this.$refs.tabs.children[i]
|
||||
if (tab.lastElementChild) {
|
||||
tab.replaceChild(it.el, tab.lastElementChild)
|
||||
} else {
|
||||
tab.append(it.el)
|
||||
let nav = this.$refs.navs.children[i]
|
||||
if (it.el) {
|
||||
nav.replaceChild(it.el, nav.firstElementChild)
|
||||
}
|
||||
if (this.tab === it.name) {
|
||||
if (gaps) {
|
||||
this.#tabWidth = nav.clientWidth
|
||||
this.#tabLeft = left
|
||||
this.#tabTop = top
|
||||
}
|
||||
this.$requestUpdate()
|
||||
it.panel.$animate()
|
||||
}
|
||||
|
||||
top += 38
|
||||
left += nav.clientWidth + gaps
|
||||
|
||||
delete it.el
|
||||
})
|
||||
}
|
||||
|
@ -70,6 +290,9 @@ class Tabs extends Component {
|
|||
selectTab(ev) {
|
||||
let elem = ev.target
|
||||
let key
|
||||
let left = 0
|
||||
let gaps = this.type === 'default' ? 32 : 0
|
||||
let top = 0
|
||||
if (elem === ev.currentTarget) {
|
||||
return
|
||||
}
|
||||
|
@ -78,15 +301,31 @@ class Tabs extends Component {
|
|||
elem = elem.parentNode
|
||||
}
|
||||
|
||||
key = elem.dataset.key
|
||||
key = this.labels[+elem.dataset.i].name
|
||||
|
||||
if (key === this.tab) {
|
||||
return
|
||||
}
|
||||
|
||||
this.#cache[this.tab].tab
|
||||
// 非默认类型, 不显示active-bar
|
||||
if (gaps) {
|
||||
this.#tabWidth = elem.clientWidth
|
||||
|
||||
for (let i = -1, it; (it = this.labels[++i]); ) {
|
||||
let nav = this.$refs.navs.children[i]
|
||||
if (key === it.name) {
|
||||
this.#tabLeft = left
|
||||
this.#tabTop = top
|
||||
break
|
||||
}
|
||||
top += 38
|
||||
left += nav.clientWidth + gaps
|
||||
}
|
||||
}
|
||||
|
||||
this.#cache[this.tab].panel
|
||||
.$animate(true)
|
||||
.then(_ => this.#cache[key].tab.$animate())
|
||||
.then(_ => this.#cache[key].panel.$animate())
|
||||
|
||||
this.tab = key
|
||||
}
|
||||
|
@ -98,9 +337,11 @@ class Tabs extends Component {
|
|||
this.labels = children.map((it, i) => {
|
||||
let tmp = {
|
||||
label: it.label,
|
||||
name: it.name || i,
|
||||
name: (it.name || i).toString(),
|
||||
el: null,
|
||||
tab: null
|
||||
panel: null,
|
||||
disabled: it.disabled,
|
||||
closable: it.closable
|
||||
}
|
||||
this.#cache[tmp.name] = tmp
|
||||
return tmp
|
||||
|
@ -112,21 +353,52 @@ class Tabs extends Component {
|
|||
this.labels[i].el = slot.cloneNode(true)
|
||||
slot.remove()
|
||||
}
|
||||
this.labels[i].tab = it
|
||||
this.labels[i].panel = it
|
||||
})
|
||||
|
||||
nextTick(_ => this.#parseSlot())
|
||||
this.tab = this.tab || this.labels[0].name
|
||||
|
||||
if (!this.tab) {
|
||||
this.tab = this.labels[0].name
|
||||
this.labels[0].tab.$animate()
|
||||
}
|
||||
nextTick(_ => this.#parseSlot())
|
||||
})
|
||||
}
|
||||
|
||||
mounted() {
|
||||
//
|
||||
}
|
||||
|
||||
render() {
|
||||
let tabStyle = {}
|
||||
if (this['tab-position'] === 'top' || this['tab-position'] === 'bottom') {
|
||||
tabStyle = {
|
||||
width: this.#tabWidth + 'px',
|
||||
left: this.#tabLeft + 'px'
|
||||
}
|
||||
} else {
|
||||
tabStyle = { top: this.#tabTop + 'px' }
|
||||
}
|
||||
|
||||
return html`
|
||||
<header class="header" ref="navs" @click=${this.selectTab}>
|
||||
${this.labels.map(
|
||||
(it, i) =>
|
||||
html`<label
|
||||
class=${classMap({
|
||||
label: true,
|
||||
active: it.name === this.tab
|
||||
})}
|
||||
title=${it.label}
|
||||
data-i=${i}
|
||||
>
|
||||
<span class="ell">${it.label}</span>
|
||||
</label>`
|
||||
)}
|
||||
<span class="active-bar" style=${styleMap(tabStyle)}></span>
|
||||
</header>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
class Tab extends Component {
|
||||
|
|
Loading…
Reference in New Issue