Skip to content

Commit

Permalink
fix(tabs): add keydown handler, fix layout bug in IE
Browse files Browse the repository at this point in the history
  • Loading branch information
SandZn authored and AngusFu committed Jan 22, 2019
1 parent 7b41d46 commit 4469538
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 63 deletions.
108 changes: 65 additions & 43 deletions src/components/tabs/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@

.c-tabs {
display: flex;

& div[role="tab"] {
box-sizing: border-box;
outline: none;
border: 2px solid transparent;
border-radius: 3px;
}

& .is-active.is-focused {
border: 2px solid var(--primary-color);
box-shadow: inset 0 0 2px color(var(--primary-color) l(var(--button-lightness-active)));
}
}

.tabs-nav {
position: relative;
display: flex;
list-style: none;
transition: color cubic-bezier(0.645, 0.045, 0.355, 1);
border: transparent;
Expand Down Expand Up @@ -37,44 +48,8 @@
}
}

.c-tabs--card {
display: block;
border: 1px solid color(var(--gray) l(90%));

& .tabs-nav {
background: color(var(--primary-color) l(50%) a(0.1));

& .tabs-nav__item {
padding: 0 1em;
border-left: 1px solid transparent;
border-right: 1px solid transparent;

&.is-active {
background-color: #fff;
position: relative;

&::after {
position: absolute;
z-index: 1;
content: '';
width: 100%;
height: 1px;
background-color: #fff;
left: 0;
bottom: -1px;
}
}

&:first-child.is-active {
border-right: 1px solid color(var(--gray) l(90%));
}

&:not(:first-child).is-active {
border-left-color: color(var(--gray) l(90%));
border-right-color: color(var(--gray) l(90%));
}
}
}
.nav-outer {
display: flex;
}

.nav-bar__active {
Expand All @@ -95,7 +70,6 @@

.c-tabs--top .tabs-nav,
.c-tabs--bottom .tabs-nav {
flex-direction: row;
border-bottom: 1px solid color(var(--gray) l(90%));

& .tabs-nav__item {
Expand All @@ -111,8 +85,11 @@
.c-tabs--left {
flex-direction: row;

& .tabs-nav {
& .nav-outer {
flex-direction: column;
}

& .tabs-nav {
border-right: 1px solid color(var(--gray) l(90%));

& .nav-bar__active {
Expand All @@ -125,8 +102,11 @@
.c-tabs--right {
flex-direction: row-reverse;

& .tabs-nav {
& .nav-outer {
flex-direction: column;
}

& .tabs-nav {
border-left: 1px solid color(var(--gray) l(90%));

& .nav-bar__active {
Expand All @@ -143,5 +123,47 @@

.tab-pane__content {
padding: 0.6em;
flex: 1;
flex-grow: 1;
}

.c-tabs--card {
display: block;
border: 1px solid color(var(--gray) l(90%));

& .tabs-nav {
display: flex;
background: color(var(--primary-color) l(50%) a(0.1));

& .tabs-nav__item {
padding: 0 1.5em;
margin-right: 0;
border-left: 1px solid transparent;
border-right: 1px solid transparent;

&.is-active {
background-color: #fff;
position: relative;

&::after {
position: absolute;
z-index: 1;
content: '';
width: 100%;
height: 1px;
background-color: #fff;
left: 0;
bottom: -1px;
}
}

&:first-child.is-active {
border-right: 1px solid color(var(--gray) l(90%));
}

&:not(:first-child).is-active {
border-left-color: color(var(--gray) l(90%));
border-right-color: color(var(--gray) l(90%));
}
}
}
}
32 changes: 27 additions & 5 deletions src/components/tabs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ layout: component
通过给`<c-tab-pane>`设置`disabled`,使当前标签页呈现不可点击状态。

```html
<c-tabs activeIndex="2">
<c-tab-pane label="video" disabled>
<c-tabs activeIndex="3">
<c-tab-pane label="video">
It's not what happens to you, but how you react to it that matters.
</c-tab-pane>
<c-tab-pane label="sun">
<c-tab-pane label="sun" disabled>
Be the type of person you want to meet.
</c-tab-pane>
<c-tab-pane label="slack">
Expand Down Expand Up @@ -144,9 +144,9 @@ export default {
```

## 事件捕捉
`@handleClicked`可捕捉触发切换标签事件。
`@change`可捕捉触发切换标签事件。
```html
<c-tabs activeIndex="2" @handleClicked="onClick">
<c-tabs activeIndex="2" @change="onClick">
<c-tab-pane label="video" disabled>
It's not what happens to you, but how you react to it that matters.
</c-tab-pane>
Expand Down Expand Up @@ -191,3 +191,25 @@ export default {
</c-tab-pane>
</c-tabs>
```

## c-tabs属性说明

| 属性 | 类型 | 默认值 | 说明 | 可选值 |
|-----|------|-------|-----|-------|
| activeIndex | String | 1 | 当前展示项索引 | 1,2,3... |
| position | String | top | 标签位置 | top / bottom / left / right |
| mode | String | - | 标签形式 | - / 'card' |

## c-tabs事件说明

| 事件 | 类型 | 说明 | 回调参数 |
|-----|-----|-----|-------|
| change | Function(activeKey) {} | 切换tab时的回调 | 被选中标签的index值 |


## c-tab-pane属性说明

| 属性 | 类型 | 默认值 | 说明 | 可选值 |
|-----|------|-------|-----|-------|
| label | String | - | 标签页标题 | - |
| disabled | Boolean | false | 禁用某一项 | false / true |
115 changes: 100 additions & 15 deletions src/components/tabs/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export default {
return {
panes: [],
currentIndex: +this.activeIndex,
reset: true
reset: true,
focusable: true,
isFocused: false
}
},
computed: {
Expand Down Expand Up @@ -65,17 +67,76 @@ export default {
if (pane.componentOptions && pane.componentOptions.propsData && pane.componentOptions.propsData.disabled) return
if (this.currentIndex === value) return
this.currentIndex = value
this.$emit('handleClicked', value)
this.$emit('change', value)
},
getPaneChildren (pane) {
if (!pane.componentOptions.children) {
return pane.componentOptions.propsData.label
}
return pane.componentOptions.children.filter(
child => child.data && child.data.slot === 'label'
)
},
getPaneContent (pane) {
if (!pane.componentOptions.children) {
return ''
}
return pane.componentOptions.children.filter(child => {
return !child.data || child.data.slot !== 'label'
})
},
clickHandler (value, pane) {
this.removeFocus()
this.setCurrentIndex(value, pane)
},
keydownHandler (e) {
e.preventDefault()
const keyCode = e.keyCode
let nextIndex
let currentIndex, tabList, validTabArray
if ([37, 38, 39, 40].indexOf(keyCode) !== -1) {
tabList = e.currentTarget.querySelectorAll('[role=tab]')
validTabArray = Array.prototype.slice.call(tabList).filter(item => +item.getAttribute('tabindex') !== -1)
currentIndex = Array.prototype.indexOf.call(validTabArray, e.target)
} else {
return
}
if (keyCode === 37 || keyCode === 38) {
if (currentIndex === 0) {
nextIndex = validTabArray.length - 1
} else {
nextIndex = currentIndex - 1
}
} else {
if (currentIndex < validTabArray.length - 1) {
nextIndex = currentIndex + 1
} else {
nextIndex = 0
}
}
validTabArray[nextIndex].focus()
validTabArray[nextIndex].click()
this.setFocus()
},
setFocus () {
if (this.focusable) {
this.isFocused = true
}
},
removeFocus () {
this.isFocused = false
}
},
render (h) {
let {
setCurrentIndex,
clickHandler,
currentIndex,
classNames,
position
position,
keydownHandler,
setFocus,
removeFocus
} = this
const panes = this.$slots.default.filter(pane => {
return pane && pane.componentOptions
})
Expand All @@ -100,25 +161,45 @@ export default {
index: index + 1,
disabled: disabled
}),
attrs: {
role: 'tab',
tabindex: disabled ? -1 : index
},
ref: `tabs${index}`,
slot: 'label',
class: `tabs-nav__item ${disabled ? 'disabled' : ''}`,
class: `tabs-nav__item ${disabled ? 'disabled' : ''} ${this.isFocused ? 'is-focused' : ''}`,
on: {
tabClicked: value => setCurrentIndex(value, pane)
tabClicked: value => clickHandler(value, pane)
},
nativeOn: {
focus: () => setFocus(),
blur: () => removeFocus()
}
},
pane.componentOptions.children.filter(child => {
return child.data && child.data.slot === 'label'
})
this.getPaneChildren(pane)
)
})
const navWrapperElem = this.mode === 'default' ? [navs, navBar] : [navs]
const navOuter = h(
'div',
{
class: 'nav-outer'
},
navs
)
const navWrapperElem = this.mode === 'default' ? [navOuter, navBar] : [navs]
const navWrapper = h(
'div',
{
class: 'tabs-nav',
ref: 'nav'
ref: 'nav',
attrs: {
role: 'tablist'
},
on: {
keydown: (event) => keydownHandler(event)
}
},
navWrapperElem
)
Expand All @@ -129,19 +210,23 @@ export default {
class: 'tab-pane__content'
},
panes.map((pane, index) => {
let ariaHidden = this.currentIndex === index + 1 ? {} : { 'aria-hidden': true }
return h(
pane.componentOptions.tag,
{
props: Object.assign(pane.componentOptions.propsData, {
shownav: false,
index: index + 1
}),
attrs: Object.assign({
id: `pane-${index + 1}`,
role: 'tabpanel',
'aria-labelledby': `tab-${index + 1}`
}, ariaHidden),
ref: `panes${index}`,
slot: 'label'
},
pane.componentOptions.children.filter(child => {
return !child.data || child.data.slot !== 'label'
})
this.getPaneContent(pane)
)
})
)
Expand Down
1 change: 1 addition & 0 deletions src/components/tabs/tab-bar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default {
}
if (['left', 'right'].indexOf(this.position) >= 0) {
return {
top: 0,
height: `${this.barHeight}px`,
transform: `translateY(${this.barOffset}px)`
}
Expand Down
Loading

0 comments on commit 4469538

Please sign in to comment.