diff --git a/.babelrc b/.babelrc index c71d366..eaf3238 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,3 @@ { - "plugins": ["transform-runtime"], "presets": ["es2015", "stage-0"] } diff --git a/demo/index.html b/demo/index.html index 7559b3f..3eec7ea 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,7 +22,7 @@
My first component - My second component + My second component
diff --git a/package.json b/package.json index 8de63ae..cc9db03 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "xtend": "^4.0.1" }, "dependencies": { - "vue": "^1.0.25" + "vue": "^1.0.25", + "vue-ripple": "^1.0.2" } } diff --git a/src/components/main.js b/src/components/main.js index 20f5b10..07dcac4 100644 --- a/src/components/main.js +++ b/src/components/main.js @@ -1,7 +1,9 @@ import Vue from 'vue/dist/vue.min'; import MdButton from './mdButton'; +import MdInkRipple from './mdInkRipple'; Vue.use(MdButton); +Vue.use(MdInkRipple); new Vue({ el: '#app', diff --git a/src/components/mdButton/mdButton.js b/src/components/mdButton/mdButton.js index 82dd649..f118481 100644 --- a/src/components/mdButton/mdButton.js +++ b/src/components/mdButton/mdButton.js @@ -4,3 +4,4 @@ export default { disabled: Boolean } }; + diff --git a/src/components/mdButton/mdButton.scss b/src/components/mdButton/mdButton.scss index 0a5f3b7..cdabc53 100644 --- a/src/components/mdButton/mdButton.scss +++ b/src/components/mdButton/mdButton.scss @@ -1,6 +1,134 @@ +@import '../../stylesheets/variables.scss'; + +$button-width: 88px; +$button-height: 36px; + +$button-icon-size: 40px; + .md-button { - height: 36px; - padding: 0 8px; - border: none; - border-radius: 2px; + min-width: $button-width; + min-height: $button-height; + margin: 6px 8px; + padding: 0 6px; + display: inline-block; + position: relative; + align-items: center; + overflow: hidden; + user-select: none; + cursor: pointer; + outline: none; + background: none; + border: 0; + border-radius: 3px; + transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function, + background-color $swift-ease-out-duration $swift-ease-out-timing-function; + color: currentColor; + font-family: inherit; + font-size: 14px; + font-style: inherit; + font-variant: inherit; + font-weight: 500; + line-height: $button-height; + text-align: center; + text-transform: uppercase; + text-decoration: none; + vertical-align: top; + white-space: nowrap; + + &:focus { + outline: none; + } + + &:hover, + &:focus { + text-decoration: none; + } + + &.ng-hide, + &.ng-leave { + transition: none; + } + + &.md-raised:not([disabled]) { + box-shadow: $material-shadow-z1; + } + + &.md-icon-button { + width: $button-icon-size; + min-width: 0; + height: $button-icon-size; + margin: 0 6px; + padding: 8px; + border-radius: 50%; + line-height: 24px; + } + +/* + &.md-cornered { + border-radius: 0; + } + + &.md-icon { + padding: 0; + background: none; + } + + &.md-fab { + + // Include the top/left/bottom/right fab positions + @include fab-all-positions(); + + z-index: $z-index-fab; + + line-height: $button-fab-line-height; + + min-width: 0; + width: $button-fab-width; + height: $button-fab-height; + vertical-align: middle; + + @include md-shadow-bottom-z-1(); + border-radius: $button-fab-border-radius; + background-clip: padding-box; + overflow: hidden; + + transition: $swift-ease-in; + transition-property: background-color, box-shadow, transform; + .md-ripple-container { + border-radius: $button-fab-border-radius; + background-clip: padding-box; + overflow: hidden; + // The following hack causes Safari/Chrome to respect overflow hidden for ripples + -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC'); + } + + &.md-mini { + line-height: $button-fab-mini-line-height; + width: $button-fab-mini-width; + height: $button-fab-mini-height; + } + + &.ng-hide, &.ng-leave { + transition: none; + } + } + + &:not([disabled]) { + &.md-raised, + &.md-fab { + &.md-focused { + @include md-shadow-bottom-z-1(); + } + &:active { + @include md-shadow-bottom-z-2(); + } + } + } */ + + .md-ripple-container { + border-radius: 3px; + background-clip: padding-box; + overflow: hidden; + -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC'); + } } diff --git a/src/components/mdButton/mdButton.vue b/src/components/mdButton/mdButton.vue index 8a819cb..bc32bd8 100644 --- a/src/components/mdButton/mdButton.vue +++ b/src/components/mdButton/mdButton.vue @@ -1,5 +1,5 @@ diff --git a/src/components/mdInkRipple/index.js b/src/components/mdInkRipple/index.js new file mode 100644 index 0000000..30b2f83 --- /dev/null +++ b/src/components/mdInkRipple/index.js @@ -0,0 +1,59 @@ +import './mdInkRipple.vue'; + +export default function install(Vue) { + let rippleParentClass = 'md-ink-ripple'; + let rippleClass = 'md-ripple'; + let rippleActiveClass = 'md-active'; + + let registerMouseEvent = (element) => { + Vue.nextTick(() => { + let rect = element.getBoundingClientRect(); + let ripple = element.querySelector('.' + rippleClass); + + element.addEventListener('mousedown', function(event) { + ripple.classList.remove(rippleActiveClass); + + let top = event.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop; + let left = event.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft; + + ripple.style.top = top + 'px'; + ripple.style.left = left + 'px'; + + ripple.classList.add(rippleActiveClass); + }); + }); + }; + + let createElement = (ripple, className, size) => { + ripple = document.createElement('div'); + ripple.className = className; + + if (size) { + ripple.style.width = size; + ripple.style.height = size; + } + + return ripple; + }; + + let createRipple = (element) => { + let ripple = element.querySelector('.' + rippleClass); + + if (!ripple) { + Vue.nextTick(() => { + let elementSize = Math.round(Math.max(element.offsetWidth, element.offsetHeight)) + 'px'; + let rippleParent = createElement(ripple, rippleParentClass); + let rippleElement = createElement(ripple, rippleClass, elementSize); + + rippleParent.appendChild(rippleElement); + element.appendChild(rippleParent); + + registerMouseEvent(element); + }); + } + }; + + Vue.directive('mdInkRipple', function() { + createRipple(this.el); + }); +} diff --git a/src/components/mdInkRipple/mdInkRipple.scss b/src/components/mdInkRipple/mdInkRipple.scss new file mode 100644 index 0000000..2c11abd --- /dev/null +++ b/src/components/mdInkRipple/mdInkRipple.scss @@ -0,0 +1,28 @@ +@import '../../stylesheets/variables.scss'; + +.md-ink-ripple { + pointer-events: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.md-ripple { + position: absolute; + transform: scale(0); + background: rgba(0, 0, 0, .26); + border-radius: 50%; + + &.md-active { + animation: ripple 1.2s $swift-ease-out-timing-function; + } +} + +@keyframes ripple { + to { + transform: scale(1.5); + opacity: 0; + } +} diff --git a/src/components/mdInkRipple/mdInkRipple.vue b/src/components/mdInkRipple/mdInkRipple.vue new file mode 100644 index 0000000..6a5ae9f --- /dev/null +++ b/src/components/mdInkRipple/mdInkRipple.vue @@ -0,0 +1 @@ + diff --git a/src/stylesheets/structure.scss b/src/stylesheets/structure.scss index e69de29..e7ac9cf 100644 --- a/src/stylesheets/structure.scss +++ b/src/stylesheets/structure.scss @@ -0,0 +1,12 @@ +body { + min-height: 100%; + 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; + font-size: 14px; + font-family: $font-roboto; +} diff --git a/src/stylesheets/variables.scss b/src/stylesheets/variables.scss index e69de29..a484ae8 100644 --- a/src/stylesheets/variables.scss +++ b/src/stylesheets/variables.scss @@ -0,0 +1,36 @@ +/* Transitions + ========================================================================== */ + +$swift-ease-out-duration: .4s !default; +$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +$swift-ease-in-duration: .3s !default; +$swift-ease-in-timing-function: cubic-bezier(.55, 0, .55, .2) !default; +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; + +$swift-ease-in-out-duration: .5s !default; +$swift-ease-in-out-timing-function: cubic-bezier(.35, 0, .25, 1) !default; +$swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default; + +$swift-linear-duration: .08s !default; +$swift-linear-timing-function: linear !default; +$swift-linear: all $swift-linear-duration $swift-linear-timing-function !default; + +$material-enter-duration: .3s; +$material-enter-timing-function: cubic-bezier(.0, .0, .2, 1); +$material-enter: all $material-enter-duration $material-enter-timing-function; + +$material-leave-duration: .3s; +$material-leave-timing-function: cubic-bezier(.4, .0, 1, 1); +$material-leave: all $material-leave-duration $material-leave-timing-function; + + + +/* Common + ========================================================================== */ + +$material-shadow-z1: 0 2px 5px rgba(0, 0, 0, .26); +$material-shadow-z2: 0 4px 8px rgba(0, 0, 0, .4); + +$font-roboto: Roboto, "Helvetica Neue", sans-serif;