Create select menu and add core directive to handle outside clicks

This commit is contained in:
Marcos Moura 2016-08-10 20:54:58 -03:00
parent 5e00d77482
commit cd77016eaf
33 changed files with 301 additions and 76 deletions

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$avatar-size: 40px;
$avatar-large-size: 64px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-bottom-bar {
width: 100%;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$button-width: 88px;
$button-height: 36px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-button-toggle {
.md-button:not([disabled]) {

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$checkbox-size: 20px;
$checkbox-ripple-size: 48px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-divider {
height: 1px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$icon-size: 24px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-ink-ripple {
pointer-events: none;

View file

@ -115,9 +115,6 @@
managePlaceholderClass(this.placeholder, this.parentClasses);
manageHasValueClass(this.$el.value, this.parentClasses);
manageMaxlength(this.maxlength, this.$parent);
},
beforeDestroy() {
}
};
</script>

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$input-size: 32px;
@ -192,11 +192,9 @@ $input-size: 32px;
}
}
&.md-has-select {
&:hover {
label {
color: rgba(#000, .87);
}
&.md-has-select:hover {
.md-select:after {
color: rgba(#000, .87);
}
}
}

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-list {
margin: 0;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$radio-size: 20px;
$radio-ripple-size: 48px;

View file

@ -1,8 +1,10 @@
import mdSelect from './mdSelect.vue';
import mdOption from './mdOption.vue';
import mdSelectTheme from './mdSelect.theme';
export default function install(Vue) {
Vue.component('md-select', Vue.extend(mdSelect));
Vue.component('md-option', Vue.extend(mdOption));
window.VueMaterial.styles.push(mdSelectTheme);
}

View file

@ -0,0 +1,28 @@
<template>
<li class="md-option" @click="selectOption" v-md-ink-ripple>
<slot></slot>
</li>
</template>
<script>
export default {
props: {
value: {
type: [String, Boolean],
required: true
}
},
methods: {
selectOption() {
this.$parent.selectOption(this.value);
}
},
ready() {
if (!this.$parent.$el.classList.contains('md-select')) {
this.$destroy();
throw new Error('You should wrap the md-option in a md-select');
}
}
};
</script>

View file

@ -1,16 +1,13 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-select {
width: 100%;
min-width: 128px;
&:hover {
&:after {
color: rgba(#000, .87);
}
}
height: 32px;
position: relative;
&:after {
margin-top: 9px;
margin-top: 2px;
position: absolute;
top: 50%;
right: 0;
@ -20,12 +17,104 @@
content: "\25BC";
}
&.md-active {
.md-select-menu {
top: -8px;
pointer-events: auto;
opacity: 1;
transform: translateY(-8px) scale3D(1, 1, 1);
transform-origin: center top;
transition: $swift-ease-out;
transition-duration: .25s;
transition-property: opacity, transform, top;
> * {
opacity: 1;
transition: $swift-ease-in;
transition-duration: .15s;
transition-delay: .1s;
}
}
}
select {
position: absolute;
left: -999em;
}
.md-select-value {
width: 100%;
height: 100%;
display: flex;
align-items: center;
cursor: pointer;
position: relative;
z-index: 2;
font-size: 16px;
line-height: 1.2em;
text-overflow: ellipsis;
white-space: nowrap;
}
.md-select-menu {
display: none;
min-width: 156px;
max-width: 100%;
min-height: 48px;
max-height: 256px;
display: flex;
flex-flow: column;
justify-content: stretch;
align-content: stretch;
pointer-events: none;
position: absolute;
top: -16px;
left: -16px;
z-index: 7;
background-color: #fff;
border-radius: 2px;
box-shadow: $material-shadow-2dp;
opacity: 0;
transform: scale3D(.85, .7, 1);
transition: opacity .25s $swift-ease-in-timing-function,
top .25s $swift-ease-in-timing-function,
transform 0s .25s $swift-ease-in-timing-function;
> * {
opacity: 0;
transition: $swift-ease-out;
transition-duration: .25s;
}
}
.md-select-menu-container {
margin: 0;
padding: 8px 0;
display: flex;
flex-flow: column;
justify-content: stretch;
align-content: stretch;
overflow-x: hidden;
overflow-y: auto;
}
}
.md-option {
height: 48px;
min-height: 48px;
padding: 0 16px;
display: flex;
flex-flow: column;
justify-content: center;
overflow: hidden;
cursor: pointer;
position: relative;
transition: $swift-ease-out;
font-size: 16px;
line-height: 1.2em;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
background-color: rgba(#000, .12);
}
}

View file

@ -1,20 +1,33 @@
<template>
<div class="md-select" :class="classes">
<div class="md-select-value">
<select v-model="model" :name="name" :id="id" :disabled="disabled" :value="value">
<option v-for="option in selectedOptions" :value="option.value">{{ options.text }}</option>
</select>
</div>
<div class="md-select" :class="classes" v-on-clickaway="hideMenu" @invalid="onInvalid" @valid="onValid">
<span class="md-select-value" @click="showMenu">{{ model }}</span>
<div class="md-select-menu">
<slot></slot>
<ul class="md-select-menu-container">
<slot></slot>
</ul>
</div>
<select v-model.sync="model" :name="name" :id="id" :required="required">
<option selected="true" :value="model">{{ model }}</option>
</select>
</div>
</template>
<style lang="scss" src="./mdSelect.scss"></style>
<script>
let hasValueClass = 'md-has-value';
let invalidClass = 'md-input-invalid';
let handleModelValue = (target, value) => {
if (value) {
target.add(hasValueClass);
} else {
target.remove(hasValueClass);
}
};
export default {
props: {
model: {
@ -22,25 +35,59 @@
required: true,
twoWay: true
},
name: {
type: String,
required: true
},
required: Boolean,
value: String,
name: String,
id: String,
disabled: Boolean
},
data() {
return {
selectedOptions: []
active: false
};
},
computed: {
classes() {
return {
'md-disabled': this.disabled
'md-disabled': this.disabled,
'md-active': this.active
};
}
},
watch: {
model(value) {
handleModelValue(this.$parent.$el.classList, value);
}
},
methods: {
onInvalid() {
this.$parent.$el.classList.add(invalidClass);
},
onValid() {
this.$parent.$el.classList.remove(invalidClass);
},
showMenu() {
this.active = true;
},
hideMenu() {
this.active = false;
},
selectOption(option) {
this.model = option;
this.hideMenu();
}
},
ready() {
if (!this.$parent.$el.classList.contains('md-input-container')) {
this.$destroy();
throw new Error('You should wrap the md-select in a md-input-container');
}
handleModelValue(this.$parent.$el.classList, this.model);
}
};
</script>

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-sidenav {
&.md-left .md-sidenav-content {

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-subheader {
min-height: 48px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$switch-width: 34px;
$switch-height: 14px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-toolbar {
min-height: 64px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
$tooltip-height: 20px;

View file

@ -1,4 +1,4 @@
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.md-whiteframe {
position: relative;

View file

@ -1,25 +1,29 @@
<script>
import CoreTheme from './core.theme';
import Vue from 'vue';
import CoreTheme from './stylesheets/core.theme';
import clickaway from './directives/clickaway';
window.VueMaterial = {
styles: [CoreTheme]
};
Vue.directive('onClickaway', clickaway);
</script>
<style lang="sass">
/* Common mixins */
@import 'utils/mixins';
@import './stylesheets/utils/mixins';
/* Commons */
@import 'utils/commons';
@import './stylesheets/utils/commons';
/* Variables */
@import 'variables';
@import './stylesheets/variables';
/* Core Styles */
@import 'structure';
@import 'type';
@import './stylesheets/structure';
@import './stylesheets/type';
</style>

View file

@ -0,0 +1,26 @@
import Vue from 'vue';
export default {
acceptStatement: true,
priority: 700,
update(handler) {
var self = this;
this.handler = function(ev) {
if (!self.el.contains(ev.target)) {
let res = handler(ev);
ev.targetVM = self.vm;
self.vm.$event = ev;
self.vm.$event = null;
return res;
}
};
Vue.util.on(document.documentElement, 'click', this.handler);
},
unbind() {
Vue.util.off(document.documentElement, 'click', this.handler);
}
};

View file

@ -1,17 +0,0 @@
body {
min-height: 100%;
margin: 0;
position: relative;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
color: rgba(#000, .87);
font-family: $font-roboto;
font-size: 14px;
font-weight: 400;
letter-spacing: .010em;
line-height: 20px;
}

View file

@ -0,0 +1,46 @@
body {
min-height: 100%;
margin: 0;
position: relative;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
color: rgba(#000, .87);
font-family: $font-roboto;
font-size: 14px;
font-weight: 400;
letter-spacing: .010em;
line-height: 20px;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
transition: $swift-ease-in-out;
background-color: rgba(0, 0, 0, .05);
&:hover {
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .05),
inset 0 -1px 0 rgba(0, 0, 0, .03);
background-color: rgba(0, 0, 0, .08);
}
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-corner {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .2);
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .05),
inset 0 -1px 0 rgba(0, 0, 0, .07);
transition: $swift-ease-in-out;
}

View file

@ -580,7 +580,7 @@
</template>
<style lang="scss">
@import '../../core/variables.scss';
@import '../../core/stylesheets/variables.scss';
.custom-list {
.md-list-action {

View file

@ -2,24 +2,29 @@
<section>
<h2 class="title">Select</h2>
<div>
<div class="field-group">
<md-input-container>
<label for="movies">Movies</label>
<md-select :model.sync="movies" name="movies" id="movies">
<label for="movie">Movie</label>
<md-select :model.sync="movie" name="movie" id="movie">
<md-option value="Fight Club">Fight Club</md-option>
<md-option value="Godfather II">Godfather II</md-option>
<md-option value="Godfather III">Godfather III</md-option>
<md-option value="Godfather">Godfather</md-option>
<md-option value="Godfellas">Godfellas</md-option>
<md-option value="Pulp Fiction">Pulp Fiction</md-option>
<md-option value="Scarface">Scarface</md-option>
</md-select>
</md-input-container>
<md-input-container>
<label for="countries">Countries</label>
<md-select :model.sync="countries" name="countries" id="countries">
<label for="country">Country</label>
<md-select :model.sync="country" name="country" id="country">
<md-option value="Australia">Australia</md-option>
<md-option value="Brazil">Brazil</md-option>
<md-option value="Japan">Japan</md-option>
<md-option value="United States">United States</md-option>
<span class="md-error">Validation message</span>
</md-select>
</md-input-container>
</div>
@ -31,7 +36,7 @@
margin: 24px;
}
div {
.field-group {
display: flex;
}
@ -44,8 +49,8 @@
export default {
data() {
return {
movies: 'Pulp Fiction',
countries: null
movie: 'Godfather',
country: ''
};
},
methods: {