From b0196b23d9ad039d60a554a43fb9ef17e0d2af42 Mon Sep 17 00:00:00 2001 From: yutent Date: Fri, 14 Apr 2023 19:08:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84tabs=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tabs/index.js | 348 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 310 insertions(+), 38 deletions(-) diff --git a/src/tabs/index.js b/src/tabs/index.js index c1a0415..f442425 100644 --- a/src/tabs/index.js +++ b/src/tabs/index.js @@ -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` - .header { - user-select: none; + :host([type='default']) { + .header { + 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` -
-
- ${this.labels.map( - (it, i) => - html`` - )} -
-
- -
-
- ` - } + #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` +
+ ${this.labels.map( + (it, i) => + html`` + )} + +
+
+ +
+ ` + } } class Tab extends Component {