Added new component, mdOnboarding

This commit is contained in:
TECNOSYLVA\fgarcia 2017-02-21 17:15:07 +01:00
parent b380dcfca6
commit 95272db85f
9 changed files with 649 additions and 0 deletions

View file

@ -99,6 +99,10 @@
<router-link exact to="/components/menu">Menu</router-link>
</md-list-item>
<md-list-item class="md-inset">
<router-link exact to="/components/onboarding">Onboarding</router-link>
</md-list-item>
<md-list-item class="md-inset">
<router-link exact to="/components/progress">Progress</router-link>
</md-list-item>

View file

@ -0,0 +1,69 @@
<template>
<page-content page-title="Components - Onboarding">
<docs-component>
<div slot="description">
<p></p>
<ul class="md-body-2">
<li><code>md-primary</code></li>
<li><code>md-warn</code></li>
</ul>
</div>
<div slot="api">
<api-table name="md-onboarding">
<md-table slot="properties">
<md-table-header>
<md-table-row>
<md-table-head>Name</md-table-head>
<md-table-head>Type</md-table-head>
<md-table-head>Description</md-table-head>
</md-table-row>
</md-table-header>
<md-table-body>
<md-table-row>
<md-table-cell>v-model</md-table-cell>
<md-table-cell><code>String</code></md-table-cell>
<md-table-cell>A required model object to bind the value.</md-table-cell>
</md-table-row>
</md-table-body>
</md-table>
</api-table>
</div>
<div slot="example">
<example-box card-title="Basic">
<div slot="demo">
<md-onboarding :md-dynamic-height="false">
<md-board id="movies" md-label="Movies">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolorum quas amet cum vitae, omnis! Illum quas voluptatem, expedita iste, dicta ipsum ea veniam dolore in, quod saepe reiciendis nihil.</p>
</md-board>
<md-board id="music" md-label="Music">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolorum quas amet cum vitae, omnis! Illum quas voluptatem, expedita iste, dicta ipsum ea veniam dolore in, quod saepe reiciendis nihil.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolorum quas amet cum vitae, omnis! Illum quas voluptatem, expedita iste, dicta ipsum ea veniam dolore in, quod saepe reiciendis nihil.</p>
</md-board>
<md-board id="books" md-label="Books">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolorum quas.</p>
</md-board>
<md-board id="pictures" md-label="Pictures" md-tooltip="This is the pictures board!">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dolorum quas.</p>
</md-board>
</md-onboarding>
</div>
<div slot="code">
<code-block lang="xml">
</code-block>
</div>
</example-box>
</div>
</docs-component>
</page-content>
</template>
<script>
</script>

View file

@ -21,6 +21,7 @@ const InkRipple = (r) => require.ensure([], () => r(require('./pages/components/
const Input = (r) => require.ensure([], () => r(require('./pages/components/Input')), 'input');
const List = (r) => require.ensure([], () => r(require('./pages/components/List')), 'list');
const Menu = (r) => require.ensure([], () => r(require('./pages/components/Menu')), 'menu');
const Onboarding = (r) => require.ensure([], () => r(require('./pages/components/Onboarding')), 'onboarding');
const Progress = (r) => require.ensure([], () => r(require('./pages/components/Progress')), 'progress');
const Radio = (r) => require.ensure([], () => r(require('./pages/components/Radio')), 'radio');
const Select = (r) => require.ensure([], () => r(require('./pages/components/Select')), 'select');
@ -148,6 +149,11 @@ const components = [
name: 'components:menu',
component: Menu
},
{
path: '/components/onboarding',
name: 'components:onboarding',
component: Onboarding
},
{
path: '/components/progress',
name: 'components:progress',

View file

@ -0,0 +1,10 @@
import mdOnboarding from './mdOnboarding.vue';
import mdBoard from './mdBoard.vue';
import mdOnboardingTheme from './mdOnboarding.theme';
export default function install(Vue) {
Vue.component('md-onboarding', mdOnboarding);
Vue.component('md-board', mdBoard);
Vue.material.styles.push(mdOnboardingTheme);
}

View file

@ -0,0 +1,105 @@
<template>
<div class="md-tab" :id="tabId" :style="styles">
<slot></slot>
</div>
</template>
<script>
import uniqueId from '../../core/utils/uniqueId';
import getClosestVueParent from '../../core/utils/getClosestVueParent';
export default {
props: {
id: [String, Number],
mdLabel: [String, Number],
mdIcon: String,
mdActive: Boolean,
mdDisabled: Boolean,
mdTooltip: String,
mdTooltipDelay: {
type: String,
default: '0'
},
mdTooltipDirection: {
type: String,
default: 'bottom'
}
},
data() {
return {
mounted: false,
tabId: this.id || 'tab-' + uniqueId(),
width: '0px',
left: '0px'
};
},
watch: {
mdActive() {
this.updateTabData();
},
mdDisabled() {
this.updateTabData();
},
mdIcon() {
this.updateTabData();
},
mdLabel() {
this.updateTabData();
},
mdTooltip() {
this.updateTabData();
},
mdTooltipDelay() {
this.updateTabData();
},
mdTooltipDirection() {
this.updateTabData();
}
},
computed: {
styles() {
return {
width: this.width,
left: this.left
};
}
},
methods: {
getTabData() {
return {
id: this.tabId,
label: this.mdLabel,
icon: this.mdIcon,
active: this.mdActive,
disabled: this.mdDisabled,
tooltip: this.mdTooltip,
tooltipDelay: this.mdTooltipDelay,
tooltipDirection: this.mdTooltipDirection,
ref: this
};
},
updateTabData() {
this.parentTabs.updateTab(this.getTabData());
}
},
mounted() {
let tabData = this.getTabData();
this.parentTabs = getClosestVueParent(this.$parent, 'md-tabs');
if (!this.parentTabs) {
throw new Error('You must wrap the md-tab in a md-tabs');
}
this.mounted = true;
this.parentTabs.updateTab(tabData);
if (this.mdActive) {
this.parentTabs.setActiveTab(tabData);
}
},
beforeDestroy() {
this.parentTabs.unregisterTab(this.getTabData());
}
};
</script>

View file

@ -0,0 +1,137 @@
@import '../../core/stylesheets/variables.scss';
$tab-width: 72px;
$tab-max-width: 264px;
.md-tabs {
width: 100%;
display: flex;
flex-flow: column;
position: relative;
&.md-transition-off * {
transition: none !important;
}
&.md-dynamic-height {
.md-tabs-content {
transition: height $swift-ease-out-duration $swift-ease-out-timing-function;
}
}
.md-tabs-navigation {
height: 48px;
min-height: 48px;
position: relative;
z-index: 1;
display: flex;
transition: $swift-ease-out;
&.md-has-icon.md-has-label {
min-height: 72px;
.md-icon {
margin-bottom: 10px;
}
}
&.md-centered {
justify-content: center;
}
&.md-fixed {
.md-tab-header {
flex: 1;
}
}
&.md-right {
justify-content: flex-end;
}
}
.md-tab-header {
min-width: $tab-width;
max-width: $tab-max-width;
margin: 0;
padding: 0 12px;
display: inline-block;
position: relative;
cursor: pointer;
border: 0;
background: none;
transition: $swift-ease-out;
font-family: inherit;
font-size: 14px;
font-weight: 500;
text-transform: uppercase;
&.md-disabled {
cursor: default;
pointer-events: none;
user-select: none;
-webkit-user-drag: none;
}
}
.md-tab-header-container {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
.md-icon {
margin: 0;
}
}
.md-tab-indicator {
height: 2px;
position: absolute;
bottom: 0;
left: 0;
transform: translate3D(0, 0, 0);
&.md-transition-off {
transition: none !important;
}
&.md-to-right {
transition: $swift-ease-out,
left .3s $swift-ease-in-out-timing-function,
right .15s $swift-ease-in-out-timing-function;
}
&.md-to-left {
transition: $swift-ease-out,
right .3s $swift-ease-in-out-timing-function,
left .15s $swift-ease-in-out-timing-function;
}
}
.md-tabs-content {
width: 100%;
height: 0;
position: relative;
overflow: hidden;
}
.md-tabs-wrapper {
width: 9999em;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translate3d(0, 0, 0);
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function;
}
.md-tab {
padding: 16px;
position: absolute;
top: 0;
left: 0;
right: 0;
}
}

View file

@ -0,0 +1,94 @@
.THEME_NAME {
&.md-tabs {
> .md-tabs-navigation {
background-color: #{'PRIMARY-COLOR'};
.md-tab-header {
color: #{'PRIMARY-CONTRAST-0.54'};
&.md-active,
&:focus {
color: #{'PRIMARY-CONTRAST'};
}
&.md-disabled {
color: #{'PRIMARY-CONTRAST-0.26'}
}
}
.md-tab-indicator {
background-color: #{'ACCENT-COLOR'};
}
}
&.md-transparent {
> .md-tabs-navigation {
background-color: transparent;
border-bottom: 1px solid #{'BACKGROUND-CONTRAST-0.12'};
.md-tab-header {
color: #{'BACKGROUND-CONTRAST-0.54'};
&.md-active,
&:focus {
color: #{'PRIMARY-COLOR'};
}
&.md-disabled {
color: #{'BACKGROUND-CONTRAST-0.26'}
}
}
.md-tab-indicator {
background-color: #{'PRIMARY-COLOR'};
}
}
}
&.md-accent {
> .md-tabs-navigation {
background-color: #{'ACCENT-COLOR'};
.md-tab-header {
color: #{'ACCENT-CONTRAST-0.54'};
&.md-active,
&:focus {
color: #{'ACCENT-CONTRAST'};
}
&.md-disabled {
color: #{'ACCENT-CONTRAST-0.26'}
}
}
.md-tab-indicator {
background-color: #{'BACKGROUND-COLOR'};
}
}
}
&.md-warn {
> .md-tabs-navigation {
background-color: #{'WARN-COLOR'};
.md-tab-header {
color: #{'WARN-CONTRAST-0.54'};
&.md-active,
&:focus {
color: #{'WARN-CONTRAST'};
}
&.md-disabled {
color: #{'WARN-CONTRAST-0.26'}
}
}
.md-tab-indicator {
background-color: #{'BACKGROUND-COLOR'};
}
}
}
}
}

View file

@ -0,0 +1,222 @@
<template>
<div class="md-tabs" :class="[themeClass, tabClasses]">
<md-whiteframe md-tag="nav" class="md-tabs-navigation" :md-elevation="mdElevation" :class="navigationClasses" ref="tabNavigation">
<button
v-for="header in tabList"
:key="header.id"
type="button"
class="md-tab-header"
:class="getHeaderClass(header)"
:disabled="header.disabled"
@click="setActiveTab(header)"
ref="tabHeader">
<md-ink-ripple :md-disabled="header.disabled"></md-ink-ripple>
<div class="md-tab-header-container">
<md-icon v-if="header.icon">{{ header.icon }}</md-icon>
<span v-if="header.label">{{ header.label }}</span>
<md-tooltip v-if="header.tooltip" :md-direction="header.tooltipDirection" :md-delay="header.tooltipDelay">{{ header.tooltip }}</md-tooltip>
</div>
</button>
<span class="md-tab-indicator" :class="indicatorClasses" ref="indicator"></span>
</md-whiteframe>
<div class="md-tabs-content" ref="tabContent" :style="{ height: contentHeight }">
<div class="md-tabs-wrapper" :style="{ transform: `translate3D(-${contentWidth}, 0, 0)` }">
<slot></slot>
</div>
</div>
</div>
</template>
<style lang="scss" src="./mdOnboarding.scss"></style>
<script>
import theme from '../../core/components/mdTheme/mixin';
import throttle from '../../core/utils/throttle';
export default {
props: {
mdFixed: Boolean,
mdCentered: Boolean,
mdRight: Boolean,
mdDynamicHeight: {
type: Boolean,
default: true
},
mdElevation: {
type: [String, Number],
default: 0
}
},
mixins: [theme],
data: () => ({
tabList: {},
activeTab: null,
activeTabNumber: 0,
hasIcons: false,
hasLabel: false,
transitionControl: null,
transitionOff: false,
contentHeight: '0px',
contentWidth: '0px'
}),
computed: {
tabClasses() {
return {
'md-dynamic-height': this.mdDynamicHeight,
'md-transition-off': this.transitionOff
};
},
navigationClasses() {
return {
'md-has-icon': this.hasIcons,
'md-has-label': this.hasLabel,
'md-fixed': this.mdFixed,
'md-right': !this.mdCentered && this.mdRight,
'md-centered': this.mdCentered || this.mdFixed
};
},
indicatorClasses() {
let toLeft = this.lastIndicatorNumber > this.activeTabNumber;
this.lastIndicatorNumber = this.activeTabNumber;
return {
'md-transition-off': this.transitionOff,
'md-to-right': !toLeft,
'md-to-left': toLeft
};
}
},
methods: {
getHeaderClass(header) {
return {
'md-active': this.activeTab === header.id,
'md-disabled': header.disabled
};
},
registerTab(tabData) {
this.tabList[tabData.id] = tabData;
},
unregisterTab(tabData) {
delete this.tabList[tabData.id];
},
updateTab(tabData) {
this.registerTab(tabData);
if (tabData.active) {
if (!tabData.disabled) {
this.setActiveTab(tabData);
} else if (Object.keys(this.tabList).length) {
let tabsIds = Object.keys(this.tabList);
let targetIndex = tabsIds.indexOf(tabData.id) + 1;
let target = tabsIds[targetIndex];
if (target) {
this.setActiveTab(this.tabList[target]);
} else {
this.setActiveTab(this.tabList[0]);
}
}
}
},
observeElementChanges() {
this.parentObserver = new MutationObserver(throttle(this.calculateOnWatch, 50));
this.parentObserver.observe(this.$refs.tabContent, {
childList: true,
attributes: true,
subtree: true
});
},
getTabIndex(id) {
const idList = Object.keys(this.tabList);
return idList.indexOf(id);
},
calculateIndicatorPos() {
if (this.$refs.tabHeader && this.$refs.tabHeader[this.activeTabNumber]) {
const tabsWidth = this.$el.offsetWidth;
const activeTab = this.$refs.tabHeader[this.activeTabNumber];
const left = activeTab.offsetLeft;
const right = tabsWidth - left - activeTab.offsetWidth;
this.$refs.indicator.style.left = left + 'px';
this.$refs.indicator.style.right = right + 'px';
}
},
calculateTabsWidthAndPosition() {
const width = this.$el.offsetWidth;
let index = 0;
this.contentWidth = width * this.activeTabNumber + 'px';
for (const tabId in this.tabList) {
const tab = this.tabList[tabId];
tab.ref.width = width + 'px';
tab.ref.left = width * index + 'px';
index++;
}
},
calculateContentHeight() {
this.$nextTick(() => {
if (Object.keys(this.tabList).length) {
let height = this.tabList[this.activeTab].ref.$el.offsetHeight;
this.contentHeight = height + 'px';
}
});
},
calculatePosition() {
window.requestAnimationFrame(() => {
this.calculateIndicatorPos();
this.calculateTabsWidthAndPosition();
this.calculateContentHeight();
});
},
debounceTransition() {
window.clearTimeout(this.transitionControl);
this.transitionControl = window.setTimeout(() => {
this.calculatePosition();
this.transitionOff = false;
}, 200);
},
calculateOnWatch() {
this.calculatePosition();
this.debounceTransition();
},
calculateOnResize() {
this.transitionOff = true;
this.calculateOnWatch();
},
setActiveTab(tabData) {
this.hasIcons = !!tabData.icon;
this.hasLabel = !!tabData.label;
this.activeTab = tabData.id;
this.activeTabNumber = this.getTabIndex(this.activeTab);
this.calculatePosition();
this.$emit('change', this.activeTabNumber);
}
},
mounted() {
this.$nextTick(() => {
this.observeElementChanges();
window.addEventListener('resize', this.calculateOnResize);
if (Object.keys(this.tabList).length && !this.activeTab) {
let firstTab = Object.keys(this.tabList)[0];
this.setActiveTab(this.tabList[firstTab]);
}
});
},
beforeDestroy() {
if (this.parentObserver) {
this.parentObserver.disconnect();
}
window.removeEventListener('resize', this.calculateOnResize);
}
};
</script>

View file

@ -16,6 +16,7 @@ import MdInputContainer from './components/mdInputContainer';
import MdLayout from './components/mdLayout';
import MdList from './components/mdList';
import MdMenu from './components/mdMenu';
import MdOnboarding from './components/mdOnboarding';
import MdProgress from './components/mdProgress';
import MdRadio from './components/mdRadio';
import MdSelect from './components/mdSelect';
@ -50,6 +51,7 @@ const options = {
MdLayout,
MdList,
MdMenu,
MdOnboarding,
MdProgress,
MdRadio,
MdSelect,