Created
July 11, 2017 21:33
-
-
Save evs-chris/808f4471be2278694c7902094dbb510a to your computer and use it in GitHub Desktop.
Tab component for 0.9+
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class-rtabs {{yield extra-attributes}}> | |
<div class-tab-window {{#if .direction === 'left'}}class-going-left{{else}}class-going-right{{/if}}> | |
<div class-tabs> | |
<div class="left-tabs"> | |
{{#each .tabs}}{{#unless .right}}{{>tab}}{{/unless}}{{/each}} | |
</div> | |
<div class="right-tabs"> | |
{{#each .tabs}}{{#if .right}}{{>tab}}{{/if}}{{/each}} | |
</div> | |
<div class="selection-indicator" style-left="{{.selectedLeft}}px" {{#if .selectedRight !== undefined}}style-right="{{.selectedRight}}px"{{/if}} /> | |
</div> | |
</div> | |
<div class-content-window | |
{{#if .transition === 'fade'}} | |
style-opacity="{{opacity}}" | |
class-trans-fade | |
{{elseif .transition === 'slide'}} | |
class-trans-slide | |
{{/if}} | |
> | |
<div class-contents style-left="{{.selectedContent * -100}}%"> | |
{{#each .tabs}}{{>tab-content}}{{/each}} | |
</div> | |
</div> | |
</div> | |
<template id="tab"> | |
<div | |
class-tab | |
{{#unless .button}}class-selected="~/selected === @index"{{/unless}} | |
{{#if .disabled}}class-disabled | |
{{elseif !.button}}on-click=['select', @index]{{/if}} | |
as-registered="@index" | |
{{#if .extraTab}}{{yield .extraTab}}{{/if}} | |
> | |
{{#if typeof .title === 'string'}}{{title}}{{elseif .title}}{{yield .title}}{{/if}} | |
{{#if .closable && !.button}}<div class-close on-click=['close', @index]>×</div>{{/if}} | |
</div> | |
</template> | |
<template id="tab-content"> | |
{{#if !.button}} | |
<div | |
class-tab | |
class-selected="~/selectedContent === @index" | |
{{#if .extra}}{{yield .extra}}{{/if}} | |
> | |
{{yield .template}} | |
</div> | |
{{else}}<div class-tab /> | |
{{/if}} | |
</template> | |
<script> | |
import Ractive from 'ractive'; | |
let resizer; | |
const instances = []; | |
class Tabs extends Ractive { | |
constructor(opts) { | |
super(opts); | |
} | |
updateIndicator() { | |
const node = this._tabs[this.get('selected')]; | |
if (node) { | |
const start = this.get('selectedLeft'); | |
if (start === undefined) { | |
this.set({ | |
selectedLeft: node.offsetLeft, | |
selectedRight: node.offsetParent.clientWidth - (node.offsetLeft + node.offsetWidth) | |
}); | |
} else { | |
const max = node.offsetParent.clientWidth; | |
const left = node.offsetLeft, width = node.clientWidth, right = max - left - width; | |
this.set({ | |
direction: left < start ? 'left' : 'right', | |
selectedLeft: left, | |
selectedRight: right | |
}); | |
} | |
} else { | |
this.set({ | |
selectedLeft: 0, | |
selectedRight: this.find('.tabs').offsetWidth | |
}); | |
} | |
} | |
} | |
const tabAttrs = ['closable', 'disabled', 'title', 'right', 'button']; | |
Ractive.extendWith(Tabs, { | |
template: $TEMPLATE, | |
cssId: 'tabs', | |
noCssTransform: true, | |
css: $CSS, | |
attributes: ['transition'], | |
data() { | |
return { | |
tabs: [], | |
rightTabs: [], | |
selected: 0, | |
selectedContent: 0, | |
opacity: 1 | |
} | |
}, | |
on: { | |
config() { | |
const tpl = this.partials.content; | |
if (tpl) { | |
const tabs = tpl.filter(n => n.e === 'tab').map(t => { | |
const tab = { | |
template: { t: t.f } | |
}; | |
const extra = []; | |
const extraTab = []; | |
t.m.forEach(a => { | |
if (a.t === 13 && ~tabAttrs.indexOf(a.n)) tab[a.n] = a.f === 0 ? true : typeof a.f === 'string' ? a.f : { t: a.f }; | |
else if (a.t === 70) extraTab.push(a); | |
else extra.push(a); | |
}); | |
if (extra.length) tab.extra = { t: extra }; | |
if (extraTab.length) tab.extraTab = { t: extraTab }; | |
return tab; | |
}); | |
tabs.unshift('tabs'); | |
this.push.apply(this, tabs); | |
if (!resizer && typeof window !== undefined) { | |
resizer = true; | |
window.addEventListener('resize', () => { | |
instances.forEach(i => i.updateIndicator()); | |
}); | |
} | |
instances.push(this); | |
} | |
}, | |
select(ctx, idx) { | |
const current = this.get('selected'); | |
const prs = []; | |
const node = this.find('.contents'); | |
const trans = this.get('transition'); | |
if (current !== idx) { | |
if (trans === 'fade') { | |
this.set({ | |
opacity: 0, | |
selected: idx | |
}); | |
this.updateIndicator(); | |
setTimeout(() => { | |
this.set({ | |
selectedContent: idx, | |
opacity: 1 | |
}); | |
}, 300); | |
} else if (trans === 'slide') { | |
this.set('selected', idx); | |
this.set('selectedContent', idx); | |
this.updateIndicator(); | |
} else { | |
this.set({ | |
selected: idx, | |
selectedContent: idx | |
}); | |
this.updateIndicator(); | |
} | |
} | |
}, | |
close(ctx, idx) { | |
const tab = this.getContext(this._tabs[idx]); | |
let ok = true; | |
if (tab.element.events.find(e => e.events.find(e => e.name === 'close'))) { | |
ok = tab.raise('close'); | |
} | |
if (ok) this.splice('tabs', idx, 1); | |
return false; | |
}, | |
teardown() { | |
instances.splice(instances.indexOf(this), 1); | |
} | |
}, | |
decorators: { | |
registered(node, idx) { | |
const me = this; | |
if (!this._tabs) this._tabs = []; | |
this._tabs[idx] = node; | |
this.updateIndicator(); | |
return { | |
teardown() {}, | |
invalidate() { | |
me.updateIndicator(); | |
}, | |
update(idx) { | |
me._tabs[idx] = node; | |
setTimeout(() => me.updateIndicator()); | |
} | |
}; | |
} | |
} | |
}); | |
export default Tabs; | |
</script> | |
<style> | |
.rtabs { | |
position: relative; | |
} | |
.rtabs > .tab-window { | |
overflow-y: hidden; | |
overflow-x: auto; | |
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), | |
0 1px 5px 0 rgba(0, 0, 0, 0.12), | |
0 3px 1px -2px rgba(0, 0, 0, 0.2); | |
} | |
.rtabs > .tab-window .tabs { | |
display: table; | |
position: relative; | |
min-width: 100%; | |
overflow-x: auto; | |
overflow-y: hidden; | |
white-space: nowrap; | |
} | |
.rtabs > .tab-window .tab { | |
display: inline-block; | |
box-sizing: border-box; | |
padding: 0.5em 1em; | |
height: 2.5em; | |
cursor: pointer; | |
opacity: 0.7; | |
transition: opacity 0.2s ease-in-out; | |
user-select: none; | |
} | |
.rtabs > .tab-window .tab:hover { | |
opacity: 1; | |
} | |
.rtabs > .tab-window .tab.selected { | |
opacity: 1; | |
} | |
.rtabs > .tab-window .tab.disabled { | |
opacity: 0.4; | |
} | |
.rtabs > .tab-window .right-tabs { | |
text-align: right; | |
display: table-cell; | |
} | |
.rtabs > .tab-window .left-tabs { | |
text-align: left; | |
display: table-cell; | |
} | |
.rtabs > .tab-window .tab > .close { | |
display: inline-block; | |
margin-right: -0.5em; | |
font-weight: 700; | |
} | |
.rtabs > .tab-window .selection-indicator { | |
position: absolute; | |
bottom: 0; | |
height: 2px; | |
background-color: blue; | |
} | |
.rtabs > .tab-window.going-left .selection-indicator { | |
transition: left 0.2s ease-in-out, right 0.2s ease-in-out 0.1s; | |
} | |
.rtabs > .tab-window.going-right .selection-indicator { | |
transition: left 0.2s ease-in-out 0.1s, right 0.2s ease-in-out; | |
} | |
.rtabs > .content-window { | |
width: 100%; | |
overflow: hidden; | |
} | |
.rtabs > .content-window > .contents { | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
position: relative; | |
white-space: nowrap; | |
left: 0; | |
} | |
.rtabs > .content-window.trans-slide > .contents { | |
transition: left 0.45s ease-in-out; | |
} | |
.rtabs > .content-window.trans-fade { | |
transition: opacity 0.3s ease; | |
} | |
.rtabs > .content-window > .contents > .tab { | |
display: inline-block; | |
width: 100%; | |
vertical-align: top; | |
white-space: initial; | |
transition: opacity 0.1s ease-in-out; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment