完善tabs组件
parent
341f096b76
commit
b0196b23d9
|
@ -4,7 +4,15 @@
|
||||||
* @date 2023/03/06 15:17:25
|
* @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 {
|
class Tabs extends Component {
|
||||||
static props = {
|
static props = {
|
||||||
|
@ -18,51 +26,263 @@ class Tabs extends Component {
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([tab-position='top']),
|
:host([tab-position='top']) {
|
||||||
:host([tab-position='bottom']) {
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
:host([tab-position='left']),
|
:host([tab-position='bottom']) {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
:host([tab-position='right']) {
|
: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`
|
css`
|
||||||
|
:host([type='default']) {
|
||||||
.header {
|
.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 = {}
|
#cache = {}
|
||||||
|
#tabWidth = 0 // 选项卡的宽度
|
||||||
render() {
|
#tabLeft = 0 // 选项卡的左侧距离
|
||||||
return html`
|
#tabTop = 0 // 选项卡的顶部距离
|
||||||
<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>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理子组件的slot,穿透到父组件来
|
// 处理子组件的slot,穿透到父组件来
|
||||||
#parseSlot() {
|
#parseSlot() {
|
||||||
|
let left = 0
|
||||||
|
let gaps = this.type === 'default' ? 32 : 0
|
||||||
|
let top = 0
|
||||||
this.labels.forEach((it, i) => {
|
this.labels.forEach((it, i) => {
|
||||||
let tab = this.$refs.tabs.children[i]
|
let nav = this.$refs.navs.children[i]
|
||||||
if (tab.lastElementChild) {
|
if (it.el) {
|
||||||
tab.replaceChild(it.el, tab.lastElementChild)
|
nav.replaceChild(it.el, nav.firstElementChild)
|
||||||
} else {
|
|
||||||
tab.append(it.el)
|
|
||||||
}
|
}
|
||||||
|
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
|
delete it.el
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -70,6 +290,9 @@ class Tabs extends Component {
|
||||||
selectTab(ev) {
|
selectTab(ev) {
|
||||||
let elem = ev.target
|
let elem = ev.target
|
||||||
let key
|
let key
|
||||||
|
let left = 0
|
||||||
|
let gaps = this.type === 'default' ? 32 : 0
|
||||||
|
let top = 0
|
||||||
if (elem === ev.currentTarget) {
|
if (elem === ev.currentTarget) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -78,15 +301,31 @@ class Tabs extends Component {
|
||||||
elem = elem.parentNode
|
elem = elem.parentNode
|
||||||
}
|
}
|
||||||
|
|
||||||
key = elem.dataset.key
|
key = this.labels[+elem.dataset.i].name
|
||||||
|
|
||||||
if (key === this.tab) {
|
if (key === this.tab) {
|
||||||
return
|
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)
|
.$animate(true)
|
||||||
.then(_ => this.#cache[key].tab.$animate())
|
.then(_ => this.#cache[key].panel.$animate())
|
||||||
|
|
||||||
this.tab = key
|
this.tab = key
|
||||||
}
|
}
|
||||||
|
@ -98,9 +337,11 @@ class Tabs extends Component {
|
||||||
this.labels = children.map((it, i) => {
|
this.labels = children.map((it, i) => {
|
||||||
let tmp = {
|
let tmp = {
|
||||||
label: it.label,
|
label: it.label,
|
||||||
name: it.name || i,
|
name: (it.name || i).toString(),
|
||||||
el: null,
|
el: null,
|
||||||
tab: null
|
panel: null,
|
||||||
|
disabled: it.disabled,
|
||||||
|
closable: it.closable
|
||||||
}
|
}
|
||||||
this.#cache[tmp.name] = tmp
|
this.#cache[tmp.name] = tmp
|
||||||
return tmp
|
return tmp
|
||||||
|
@ -112,21 +353,52 @@ class Tabs extends Component {
|
||||||
this.labels[i].el = slot.cloneNode(true)
|
this.labels[i].el = slot.cloneNode(true)
|
||||||
slot.remove()
|
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) {
|
nextTick(_ => this.#parseSlot())
|
||||||
this.tab = this.labels[0].name
|
|
||||||
this.labels[0].tab.$animate()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
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 {
|
class Tab extends Component {
|
||||||
|
|
Loading…
Reference in New Issue