Selaa lähdekoodia

add tabs scroll controls

baiyaaaaa 8 vuotta sitten
vanhempi
commit
6702726dcf

+ 3 - 0
packages/tabs/src/tab-bar.vue

@@ -3,9 +3,12 @@
 </template>
 <script>
   export default {
+    name: 'TabBar',
+
     props: {
       tabs: Array
     },
+
     computed: {
       barStyle: {
         cache: false,

+ 174 - 0
packages/tabs/src/tab-nav.vue

@@ -0,0 +1,174 @@
+<script>
+  import TabBar from './tab-bar';
+
+  function noop() {}
+
+  export default {
+    name: 'TabNav',
+
+    components: {
+      TabBar
+    },
+
+    props: {
+      panes: Array,
+      currentName: String,
+      editable: Boolean,
+      onTabClick: {
+        type: Function,
+        default: noop
+      },
+      onTabRemove: {
+        type: Function,
+        default: noop
+      },
+      type: String
+    },
+
+    data() {
+      return {
+        scrollable: false,
+        navStyle: {
+          transform: ''
+        }
+      };
+    },
+
+    methods: {
+      scrollPrev() {
+        const containerWidth = this.$refs.navScroll.offsetWidth;
+        const currentOffset = this.getCurrentScrollOffset();
+
+        if (!currentOffset) return;
+
+        let newOffset = currentOffset > containerWidth
+          ? currentOffset - containerWidth
+          : 0;
+
+        this.setOffset(newOffset);
+      },
+      scrollNext() {
+        const navWidth = this.$refs.nav.offsetWidth;
+        const containerWidth = this.$refs.navScroll.offsetWidth;
+        const currentOffset = this.getCurrentScrollOffset();
+
+        if (navWidth - currentOffset <= containerWidth) return;
+
+        let newOffset = navWidth - currentOffset > containerWidth * 2
+          ? currentOffset + containerWidth
+          : (navWidth - containerWidth);
+
+        this.setOffset(newOffset);
+      },
+      scrollToActiveTab() {
+        if (!this.scrollable) return;
+        const nav = this.$refs.nav;
+        const activeTab = this.$el.querySelector('.is-active');
+        const navScroll = this.$refs.navScroll;
+        const activeTabBounding = activeTab.getBoundingClientRect();
+        const navScrollBounding = navScroll.getBoundingClientRect();
+        const navBounding = nav.getBoundingClientRect();
+        const currentOffset = this.getCurrentScrollOffset();
+        let newOffset = currentOffset;
+
+        if (activeTabBounding.left < navScrollBounding.left) {
+          newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
+        }
+        if (activeTabBounding.right > navScrollBounding.right) {
+          newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
+        }
+        if (navBounding.right < navScrollBounding.right) {
+          newOffset = nav.offsetWidth - navScrollBounding.width;
+        }
+        this.setOffset(Math.max(newOffset, 0));
+      },
+      getCurrentScrollOffset() {
+        const { navStyle } = this;
+        return navStyle.transform
+          ? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
+          : 0;
+      },
+      setOffset(value) {
+        this.navStyle.transform = `translateX(-${value}px)`;
+      }
+    },
+
+    updated() {
+      const navWidth = this.$refs.nav.offsetWidth;
+      const containerWidth = this.$refs.navScroll.offsetWidth;
+      const currentOffset = this.getCurrentScrollOffset();
+
+      if (containerWidth < navWidth) {
+        const currentOffset = this.getCurrentScrollOffset();
+        this.scrollable = this.scrollable || {};
+        this.scrollable.prev = currentOffset;
+        this.scrollable.next = currentOffset + containerWidth < navWidth;
+        if (navWidth - currentOffset < containerWidth) {
+          this.setOffset(navWidth - containerWidth);
+        }
+      } else if (currentOffset > 0) {
+        this.setOffset(0);
+      }
+    },
+
+    render(h) {
+      const {
+        type,
+        panes,
+        editable,
+        onTabClick,
+        onTabRemove,
+        navStyle,
+        scrollable,
+        scrollNext,
+        scrollPrev
+      } = this;
+
+      const scrollBtn = scrollable
+      ? [
+        <span class={['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled']} on-click={scrollPrev}><i class="el-icon-arrow-left"></i></span>,
+        <span class={['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled']} on-click={scrollNext}><i class="el-icon-arrow-right"></i></span>
+      ] : null;
+
+      const tabs = this._l(panes, (pane, index) => {
+        let tabName = pane.name || pane.index || index;
+        const closable = pane.isClosable || editable;
+
+        pane.index = `${index}`;
+
+        const btnClose = closable
+          ? <span class="el-icon-close" on-click={(ev) => { onTabRemove(pane, ev); }}></span>
+          : null;
+
+        const tabLabelContent = pane.$slots.label || pane.label;
+        return (
+          <div
+            class={{
+              'el-tabs__item': true,
+              'is-active': pane.active,
+              'is-disabled': pane.disabled,
+              'is-closable': closable
+            }}
+            ref="tabs"
+            refInFor
+            on-click={(ev) => { onTabClick(pane, tabName, ev); }}
+          >
+            {tabLabelContent}
+            {btnClose}
+          </div>
+        );
+      });
+      return (
+        <div class={['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '']}>
+          {scrollBtn}
+          <div class={['el-tabs__nav-scroll']} ref="navScroll">
+            <div class="el-tabs__nav" ref="nav" style={navStyle}>
+              {!type ? <tab-bar tabs={panes}></tab-bar> : null}
+              {tabs}
+            </div>
+          </div>
+        </div>
+      );
+    }
+  };
+</script>

+ 28 - 38
packages/tabs/src/tabs.vue

@@ -1,10 +1,11 @@
 <script>
-  import TabBar from './tab-bar';
+  import TabNav from './tab-nav';
+
   module.exports = {
     name: 'ElTabs',
 
     components: {
-      TabBar
+      TabNav
     },
 
     props: {
@@ -18,7 +19,6 @@
 
     data() {
       return {
-        children: null,
         currentName: this.value || this.activeName,
         panes: []
       };
@@ -30,6 +30,13 @@
       },
       value(value) {
         this.setCurrentName(value);
+      },
+      currentName(value) {
+        if (this.$refs.nav) {
+          this.$nextTick(_ => {
+            this.$refs.nav.scrollToActiveTab();
+          });
+        }
       }
     },
 
@@ -78,7 +85,7 @@
       const newButton = editable || addable
         ? (
             <span
-              class="el-tabs__new-button"
+              class="el-tabs__new-tab"
               on-click={ handleTabAdd }
             >
                 <i class="el-icon-plus"></i>
@@ -86,38 +93,17 @@
           )
         : null;
 
-      const tabs = this._l(panes, (pane, index) => {
-        let tabName = pane.name || pane.index || index;
-        const closable = pane.isClosable || editable;
-
-        if (currentName === undefined && index === 0) {
-          this.setCurrentName(tabName);
-        }
-
-        pane.index = index;
-
-        const btnClose = closable
-          ? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(pane, ev); }}></span>
-          : null;
-
-        const tabLabelContent = pane.$slots.label || pane.label;
-        return (
-          <div
-            class={{
-              'el-tabs__item': true,
-              'is-active': pane.active,
-              'is-disabled': pane.disabled,
-              'is-closable': closable
-            }}
-            ref="tabs"
-            refInFor
-            on-click={(ev) => { handleTabClick(pane, tabName, ev); }}
-          >
-            {tabLabelContent}
-            {btnClose}
-          </div>
-        );
-      });
+      const navData = {
+        props: {
+          currentName,
+          onTabClick: handleTabClick,
+          onTabRemove: handleTabRemove,
+          editable,
+          type,
+          panes
+        },
+        ref: 'nav'
+      };
 
       return (
         <div class={{
@@ -126,15 +112,19 @@
           'el-tabs--border-card': type === 'border-card'
         }}>
           <div class="el-tabs__header">
-            {!type ? <tab-bar tabs={panes}></tab-bar> : null}
-            {tabs}
             {newButton}
+            <tab-nav { ...navData }></tab-nav>
           </div>
           <div class="el-tabs__content">
             {this.$slots.default}
           </div>
         </div>
       );
+    },
+    created() {
+      if (!this.currentName) {
+        this.setCurrentName('1');
+      }
     }
   };
 </script>

+ 37 - 8
packages/theme-default/src/tabs.css

@@ -8,7 +8,6 @@
       padding: 0;
       position: relative;
       margin: 0 0 15px;
-      @utils-clearfix;
     }
     @e active-bar {
       position: absolute;
@@ -20,8 +19,8 @@
       transition: transform .3s cubic-bezier(.645,.045,.355,1);
       list-style: none;
     }
-    @e new-button {
-      float: left;
+    @e new-tab {
+      float: right;
       border: 1px solid #d3dce6;
       height: 18px;
       width: @height;
@@ -42,16 +41,46 @@
         color: var(--color-primary);
       }
     }
+    @e nav-wrap {
+      overflow: hidden;
+      margin-bottom: -1px;
+      position: relative;
+
+      @when scrollable {
+        padding: 0 15px;
+      }
+    }
+    @e nav-scroll {
+      overflow: hidden;
+    }
+    @e nav-next, nav-prev {
+      position: absolute;
+      cursor: pointer;
+      line-height: 44px;
+      font-size: 12px;
+      color: var(--color-base-silver);
+    }
+    @e nav-next {
+      right: 0;
+    }
+    @e nav-prev {
+      left: 0;
+    }
+    @e nav {
+      white-space: nowrap;
+      position: relative;
+      float: left;
+      transition: transform .3s;
+    }
     @e item {
       padding: 0 16px;
       height: 42px;
       box-sizing: border-box;
       line-height: @height;
-      float: left;
+      display: inline-block;
       list-style: none;
       font-size: 14px;
       color: var(--color-base-silver);
-      margin-bottom: -1px;
       position: relative;
 
       & .el-icon-close {
@@ -89,10 +118,10 @@
       position: relative;
     }
     @m card {
-      &>.el-tabs__header>.el-tabs__active-bar {
+      .el-tabs__nav .el-tabs__active-bar {
         display: none;
       }
-      &>.el-tabs__header>.el-tabs__item .el-icon-close {
+      .el-tabs__nav .el-tabs__item .el-icon-close {
         position: relative;
         font-size: 12px;
         width: 0;
@@ -104,7 +133,7 @@
         right: -2px;
         transform-origin: 100% 50%;
       }
-      &>.el-tabs__header>.el-tabs__item {
+      .el-tabs__nav .el-tabs__item {
         border: 1px solid transparent;
         transition: all .3s cubic-bezier(.645,.045,.355,1);