diff --git a/gulpfile.js b/gulpfile.js index a639ea24b4..d3336121f4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,6 +1,8 @@ +var autoprefixer = require('gulp-autoprefixer'); var format = require('gulp-clang-format'); var gulp = require('gulp'); var gulpPlugins = require('gulp-load-plugins')(); +var sass = require('gulp-sass'); var shell = require('gulp-shell'); var runSequence = require('run-sequence'); var madge = require('madge'); @@ -152,7 +154,7 @@ var CONFIG = { src: ['modules/**/*.css'], pipes: {} } - }, + } }, multicopy: { js: { @@ -697,3 +699,23 @@ gulp.task('build.js', ['build.js.dev', 'build.js.prod', 'build.js.cjs', 'bundle. gulp.task('clean', ['build/clean.js', 'build/clean.dart', 'build/clean.docs']); gulp.task('build', ['build.js', 'build.dart']); + + +// ------------ +// angular material testing rules +gulp.task('build/css.js.dev', function() { + return gulp.src('modules/*/src/**/*.scss') + .pipe(sass()) + .pipe(autoprefixer()) + .pipe(gulp.dest(CONFIG.dest.js.dev.es5)); +}); + +// TODO: this target is temporary until we find a way to use the SASS transformer +gulp.task('build/css.dart', function() { + return gulp.src('dist/dart/angular2_material/lib/src/**/*.scss') + .pipe(sass()) + .pipe(autoprefixer()) + .pipe(gulp.dest('dist/dart/angular2_material/lib/src')); +}); + +gulp.task('build.material', ['build.js.dev', 'build/css.js.dev']); diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 1f240e8ac3..c829d562fe 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -1,5 +1,6 @@ -import {Injector, bind, OpaqueToken} from 'angular2/di'; -import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print, stringify} from 'angular2/src/facade/lang'; +import {Injector, bind, OpaqueToken, Optional} from 'angular2/di'; +import {NumberWrapper, Type, isBlank, isPresent, BaseException, + assertionsEnabled, print, stringify} from 'angular2/src/facade/lang'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {Compiler, CompilerCache} from './compiler/compiler'; diff --git a/modules/angular2/src/facade/math.dart b/modules/angular2/src/facade/math.dart index 034723fd5d..8e5cfa9d43 100644 --- a/modules/angular2/src/facade/math.dart +++ b/modules/angular2/src/facade/math.dart @@ -10,6 +10,8 @@ class Math { return math.pow(x, exponent); } + static num max(num a, num b) => math.max(a, b); + static num min(num a, num b) => math.min(a, b); static num floor(num a) => a.floor(); diff --git a/modules/angular2_material/README.md b/modules/angular2_material/README.md new file mode 100644 index 0000000000..0e0d3515a3 --- /dev/null +++ b/modules/angular2_material/README.md @@ -0,0 +1 @@ +Temporary home of Material Design components for Angular 2. \ No newline at end of file diff --git a/modules/angular2_material/package.json b/modules/angular2_material/package.json new file mode 100644 index 0000000000..fa08e91ea7 --- /dev/null +++ b/modules/angular2_material/package.json @@ -0,0 +1,17 @@ +{ + "name": "angular2_material", + "version": "<%= packageJson.version %>", + "description": "Google material design components for Angular 2", + "homepage": "<%= packageJson.homepage %>", + "bugs": "<%= packageJson.bugs %>", + "contributors": <%= JSON.stringify(packageJson.contributors) %>, + "license": "<%= packageJson.license %>", + "dependencies": { + "angular2": "<%= packageJson.version %>", + "traceur": "<%= packageJson.dependencies.traceur %>", + "rtts_assert": "<%= packageJson.version %>", + "rx": "<%= packageJson.dependencies['rx'] %>", + "zone.js": "<%= packageJson.dependencies['zone.js'] %>" + }, + "devDependencies": <%= JSON.stringify(packageJson.devDependencies) %> +} diff --git a/modules/angular2_material/pubspec.yaml b/modules/angular2_material/pubspec.yaml new file mode 100644 index 0000000000..cb9c8ba496 --- /dev/null +++ b/modules/angular2_material/pubspec.yaml @@ -0,0 +1,16 @@ +name: angular2_material +version: <%= packageJson.version %> +authors: +<%= Object.keys(packageJson.contributors).map(function(name) { + return '- '+name+' <'+packageJson.contributors[name]+'>'; +}).join('\n') %> +description: Google material design components for Angular 2 +homepage: <%= packageJson.homepage %> +environment: + sdk: '>=1.9.0-dev.8.0' +dependencies: + angular2: '^<%= packageJson.version %>' + browser: '>=0.10.0 <0.11.0' +dependency_overrides: + angular2: + path: ../angular2 diff --git a/modules/angular2_material/src/components/button/button.html b/modules/angular2_material/src/components/button/button.html new file mode 100644 index 0000000000..1b07d41899 --- /dev/null +++ b/modules/angular2_material/src/components/button/button.html @@ -0,0 +1,2 @@ + + diff --git a/modules/angular2_material/src/components/button/button.js b/modules/angular2_material/src/components/button/button.js new file mode 100644 index 0000000000..232c70bd89 --- /dev/null +++ b/modules/angular2_material/src/components/button/button.js @@ -0,0 +1,47 @@ +import {Component, View} from 'angular2/angular2'; +import {PropertySetter, EventEmitter} from 'angular2/src/core/annotations/di'; +import {onChange} from 'angular2/src/core/annotations/annotations'; +import {isPresent, StringWrapper} from 'angular2/src/facade/lang'; + + +@Component({selector: '[md-button]:not([href])'}) +@View({templateUrl: 'angular2_material/src/components/button/button.html'}) +export class MdButton { + // TODO(jelbourn): Ink ripples. +} + + +@Component({ + selector: '[md-button][href]', + properties: { + 'disabled': 'disabled' + }, + hostListeners: {'click': 'onClick($event)'}, + lifecycle: [onChange] +}) +@View({ + templateUrl: 'angular2_material/src/components/button/button.html' +}) +export class MdAnchor { + tabIndexSetter: Function; + + /** Whether the component is disabled. */ + disabled: boolean; + + constructor(@PropertySetter('tabIndex') tabIndexSetter: Function) { + this.tabIndexSetter = tabIndexSetter; + } + + onClick(event) { + // A disabled anchor shouldn't navigate anywhere. + if (isPresent(this.disabled) && this.disabled !== false) { + event.preventDefault(); + } + } + + /** Invoked when a change is detected. */ + onChange(_) { + // A disabled anchor should not be in the tab flow. + this.tabIndexSetter(this.disabled ? -1 : 0); + } +} diff --git a/modules/angular2_material/src/components/button/button.scss b/modules/angular2_material/src/components/button/button.scss new file mode 100644 index 0000000000..aa73c6d3a6 --- /dev/null +++ b/modules/angular2_material/src/components/button/button.scss @@ -0,0 +1,349 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$button-line-height: 25px !default; +$button-padding: 2px 6px 3px !default; + +// Fab buttons +$button-fab-width: 56px !default; +$button-fab-height: 56px !default; +$button-fab-padding: 16px !default; +$button-fab-mini-width: 40px !default; +$button-fab-mini-height: 40px !default; + +$button-fab-toast-offset: $button-fab-height * 0.75; + +/** + * Mixin to create distinct classes for fab positions, e.g. ".md-fab-bottom-right". + */ +@mixin fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) { + &.md-fab-#{$spot} { + top: $top; + right: $right; + bottom: $bottom; + left: $left; + position: absolute; + } +} + +[md-button] { + user-select: none; + + // TODO(jelbourn): What canvas? + position: relative; //for child absolute-positioned + + outline: none; + border: 0; + padding: 6px; + margin: 0; + background: transparent; + white-space: nowrap; + + text-align: center; + + // TODO(jelbourn): wat? + font-weight: 500; + font-style: inherit; + font-variant: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; + text-decoration: none; + + cursor: pointer; + overflow: hidden; // for ink containment + + transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function, + background-color $swift-ease-out-duration $swift-ease-out-timing-function, + transform $swift-ease-out-duration $swift-ease-out-timing-function; + + &:focus { + outline: none; + } + + &:hover { + text-decoration: none; + } + + &.md-cornered { + border-radius: 0; + } + + &.md-icon { + padding: 0; + background: none; + } + + &.md-raised { + transform: translate3d(0, 0, 0); + + + &:not([disabled]) { + @extend .md-shadow-bottom-z-1; + } + } + + &.md-fab { + // .md-fab-bottom-right + @include fab-position( + bottom-right, + auto, + ($button-fab-width - $button-fab-padding) / 2, + ($button-fab-height - $button-fab-padding) / 2, + auto); + + // .md-fab-bottom-left + @include fab-position( + bottom-left, + auto, + auto, + ($button-fab-height - $button-fab-padding) / 2, + ($button-fab-width - $button-fab-padding) / 2); + + // .md-fab-top-right + @include fab-position( + top-right, + ($button-fab-height - $button-fab-padding) / 2, + ($button-fab-width - $button-fab-padding) / 2, + auto, + auto); + + // .md-fab-top-left + @include fab-position( + top-left, + ($button-fab-height - $button-fab-padding) / 2, + auto, + auto, + ($button-fab-width - $button-fab-padding) / 2); + + + z-index: $z-index-fab; + + width: $button-fab-width; + height: $button-fab-height; + + border-radius: 50%; + + @extend .md-shadow-bottom-z-1; + overflow: hidden; + + transform: translate3d(0, 0, 0); + + transition: 0.2s linear; + transition-property: transform, box-shadow; + + + // When there is an md-icon inside of an [md-button]. + & md-icon { + line-height: $button-fab-height; + margin-top: 0; + } + + &.md-mini { + width: $button-fab-mini-width; + height: $button-fab-mini-height; + md-icon { + line-height: $button-fab-mini-height; + } + } + + } + + &:not([disabled]) { + &.md-raised, + &.md-fab { + &:focus, + &:hover { + transform: translate3d(0, -1px, 0); + @extend .md-shadow-bottom-z-2; + } + } + } +} + +// TODO +.md-toast-open-top { + [md-button].md-fab-top-left, + [md-button].md-fab-top-right { + transform: translate3d(0, $button-fab-toast-offset, 0); + &:not([disabled]) { + &:focus, + &:hover { + transform: translate3d(0, $button-fab-toast-offset - 1, 0); + } + } + } +} + +// TODO +.md-toast-open-bottom { + [md-button].md-fab-bottom-left, + [md-button].md-fab-bottom-right { + transform: translate3d(0, -$button-fab-toast-offset, 0); + &:not([disabled]) { + &:focus, + &:hover { + transform: translate3d(0, -$button-fab-toast-offset - 1, 0); + } + } + } +} + +// TODO +.md-button-group { + display: flex; + flex: 1; + width: 100%; +} + +// TODO +.md-button-group > [md-button] { + flex: 1; + + display: block; + + overflow: hidden; + + width: 0; + + border-width: 1px 0 1px 1px; + border-radius: 0; + + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + + &:first-child { + border-radius: 2px 0 0 2px; + } + &:last-child { + border-right-width: 1px; + border-radius: 0 2px 2px 0; + } +} + + +// ------------------------ Button theme ---------------------------------------------------------- + +$button-border-radius: 3px !default; +$button-fab-border-radius: 50% !default; + +// The ELEMENT '[md-button]' is the host. +// The CLASS '[md-button]' is the shadow root. + +md-toolbar [md-button].md-fab { + background-color: white; +} + + +// Because of Shadow DOM emulation, the modifier classes are applied to the [md-button] +// element (host), but the style needs to be changed for the shadow content (.[md-button]). +[md-button] { + border-radius: $button-border-radius; + + // When the user explictly applies the primary color. + &.md-primary { + color: md-color($md-primary); + } + + &.md-primary.md-fab, + &.md-primary.md-raised { + color: md-color($md-primary, default-contrast); + background-color: md-color($md-primary); + } + + // When the user explictly applies the accent color. + &.md-accent { + color: md-color($md-accent); + } + + &.md-accent.md-fab, + &.md-accent.md-raised { + color: md-color($md-accent, default-contrast); + background-color: md-color($md-accent); + } + + // When the user explictly applies the warn color. + &.md-warn { + color: md-color($md-warn); + } + + &.md-warn.md-fab, + &.md-warn.md-raised { + color: md-color($md-warn, default-contrast); + background-color: md-color($md-warn); + } + + // When the button is a fab without an explicit color applied. + &.md-fab { + border-radius: $button-fab-border-radius; + background-color: md-color($md-accent); + color: md-color($md-accent, default-contrast); + } + + // When the button is raised without an explicit color applied. + &.md-raised { + color: md-color($md-background, default-contrast); + background-color: md-color($md-background, 50); + } +} + +// Hover and focus styles (only applied when the button is not disabled). +// The modifiers for :focus and :hover actually apply to the element in the +// shadow DOM (the .[md-button]). +[md-button]:not([disabled]) { + // Default hover/focus background. + &:focus, + &:hover { + background-color: md-color($md-background, 500, 0.2); + } + + &.md-primary.md-fab, + &.md-primary.md-raised { + &:hover, + &:focus { + background-color: md-color($md-primary, 600); + } + } + + &.md-accent.md-fab, + &.md-accent.md-raised { + &:hover, + &:focus { + background-color: md-color($md-accent, 700); + } + } + + &.md-warn.md-fab, + &.md-warn.md-raised { + &:hover, + &:focus { + background-color: md-color($md-warn, 700); + } + } + + &.md-fab { + &:hover, + &:focus { + background-color: md-color($md-accent, A700); + } + } + + &.md-raised { + &:hover, + &:focus { + background-color: md-color($md-background, 200); + } + } +} + +[md-button][disabled], +[md-button][disabled].md-fab, +[md-button][disabled].md-raised, { + color: md-color($md-foreground, disabled); + background-color: transparent; + cursor: not-allowed; +} diff --git a/modules/angular2_material/src/components/checkbox/checkbox.html b/modules/angular2_material/src/components/checkbox/checkbox.html new file mode 100644 index 0000000000..91e01a488b --- /dev/null +++ b/modules/angular2_material/src/components/checkbox/checkbox.html @@ -0,0 +1,8 @@ + + +
+
+
+
+
+
diff --git a/modules/angular2_material/src/components/checkbox/checkbox.js b/modules/angular2_material/src/components/checkbox/checkbox.js new file mode 100644 index 0000000000..dcd27a5079 --- /dev/null +++ b/modules/angular2_material/src/components/checkbox/checkbox.js @@ -0,0 +1,77 @@ +import {Component, View, Attribute, PropertySetter} from 'angular2/angular2'; +import {isPresent} from 'angular2/src/facade/lang'; +import {KeyCodes} from 'angular2_material/src/core/constants' + +@Component({ + selector: 'md-checkbox', + properties: { + 'checked': 'checked', + 'disabled': 'disabled' + }, + hostListeners: { + 'keydown': 'onKeydown($event)' + } +}) +@View({ + templateUrl: 'angular2_material/src/components/checkbox/checkbox.html', + directives: [] +}) +export class MdCheckbox { + /** Whether this checkbox is checked. */ + checked_: boolean; + + /** Setter for `aria-checked` attribute. */ + ariaCheckedSetter: Function; + + /** Setter for `aria-disabled` attribute. */ + ariaDisabledSetter: Function; + + constructor( + @Attribute('tabindex') tabindex: string, + @PropertySetter('tabindex') tabindexSetter: Function, + @PropertySetter('attr.role') roleSetter: Function, + @PropertySetter('attr.aria-checked') ariaCheckedSetter: Function, + @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function) { + this.ariaCheckedSetter = ariaCheckedSetter; + this.ariaDisabledSetter = ariaDisabledSetter; + + roleSetter('checkbox'); + this.checked = false; + tabindexSetter(isPresent(tabindex) ? tabindex : '0'); + } + + get checked() { + return this.checked_; + } + + set checked(value) { + this.checked_ = value; + this.ariaCheckedSetter(value); + } + + get disabled() { + return this.disabled_; + } + + set disabled(value) { + this.disabled_ = isPresent(value) && value !== false; + this.ariaDisabledSetter(this.disabled_); + } + + onKeydown(event: KeyboardEvent) { + if (event.keyCode == KeyCodes.SPACE) { + event.preventDefault(); + this.toggle(event); + } + } + + toggle(event) { + if (this.disabled) { + event.stopPropagation(); + return; + } + + this.checked = !this.checked; + this.ariaCheckedSetter(this.checked); + } +} diff --git a/modules/angular2_material/src/components/checkbox/checkbox.scss b/modules/angular2_material/src/components/checkbox/checkbox.scss new file mode 100644 index 0000000000..7764f5bf0d --- /dev/null +++ b/modules/angular2_material/src/components/checkbox/checkbox.scss @@ -0,0 +1,176 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$checkbox-width: 18px !default; +$checkbox-height: $checkbox-width !default; + +md-checkbox { + box-sizing: border-box; + display: block; + margin: 15px; + white-space: nowrap; + cursor: pointer; + outline: none; + user-select: none; + + *, *:after { + box-sizing: border-box; + } + + &[aria-checked="true"] .md-checkbox-icon { + border: none; + } + + // Checkbox is disabled. + &[disabled] { + cursor: no-drop; + } + + // Checkbox is focused. + &:focus .md-checkbox-label:not(:empty) { + border-color: black; + } + + // Checkbox is checked. + &[aria-checked="true"] .md-checkbox-icon:after { + transform: rotate(45deg); + position: absolute; + left: 6px; + top: 2px; + display: table; + width: 6px; + height: 12px; + border: 2px solid; + border-top: 0; + border-left: 0; + content: ' '; + } +} + +.md-checkbox-container { + position: relative; + top: 4px; + display: inline-block; + width: $checkbox-width; + height: $checkbox-height; + + &:after { + content: ''; + position: absolute; + top: -10px; + right: -10px; + bottom: -10px; + left: -10px; + } + + .md-ripple-container { + position: absolute; + display: block; + width: auto; + height: auto; + left: -15px; + top: -15px; + right: -15px; + bottom: -15px; + } +} + +// Checkbox is not checked. +.md-checkbox-icon { + transition: 240ms; + position: absolute; + top: 0; + left: 0; + width: $checkbox-width; + height: $checkbox-height; + border: 2px solid; + border-radius: 2px; +} + +.md-checkbox-label { + border: 1px dotted transparent; + position: relative; + display: inline-block; + margin-left: 10px; + vertical-align: middle; + white-space: normal; + pointer-events: none; + user-select: text; +} + +// THEME +// TODO(jelbourn): ripple + +md-checkbox { + + .md-ripple { + color: md-color($md-accent, 600); + } + &[aria-checked="true"] .md-ripple { + color: md-color($md-background, 600); + } + + .md-checkbox-icon { + border-color: md-color($md-foreground, icon); + } + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-accent, 0.87); + } + + &[aria-checked="true"] .md-checkbox-icon:after { + border-color: md-color($md-background, 200); + } + + &:not([disabled]) { + &.md-primary { + .md-ripple { + color: md-color($md-primary, 600); + } + &[aria-checked="true"] .md-ripple { + color: md-color($md-background, 600); + } + + .md-checkbox-icon { + border-color: md-color($md-foreground, icon); + } + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-primary, 0.87); + } + + &[aria-checked="true"] .md-checkbox-icon:after { + border-color: md-color($md-background, 200); + } + } + + &.md-warn { + .md-ripple { + color: md-color($md-warn, 600); + } + + .md-checkbox-icon { + border-color: md-color($md-foreground, icon); + } + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-warn, 0.87); + } + + &[aria-checked="true"] .md-checkbox-icon:after { + border-color: md-color($md-background, 200); + } + } + } + + &[disabled] { + .md-checkbox-icon { + border-color: md-color($md-foreground, disabled); + } + + &[aria-checked="true"] .md-checkbox-icon { + background-color: md-color($md-foreground, disabled); + } + } + +} diff --git a/modules/angular2_material/src/components/grid_list/grid-list.scss b/modules/angular2_material/src/components/grid_list/grid-list.scss new file mode 100644 index 0000000000..fb8d0acf7a --- /dev/null +++ b/modules/angular2_material/src/components/grid_list/grid-list.scss @@ -0,0 +1,68 @@ +md-grid-list { + display: block; + position: relative; +} + +md-grid-tile { + display: block; + position: absolute; + + figure { + display: flex; + position: absolute; + + align-items: center; + justify-content: center; + height: 100%; + + top: 0; + right: 0; + bottom: 0; + left: 0; + + padding: 0; + margin: 0; + } + + // Headers & footers + md-grid-tile-header, + md-grid-tile-footer { + display: flex; + flex-direction: row; + align-items: center; + height: 48px; + color: #fff; + background: rgba(0, 0, 0, 0.18); + overflow: hidden; + + // Positioning + position: absolute; + left: 0; + right: 0; + + h3, + h4 { + font-weight: 400; + margin: 0 0 0 16px; + } + + h3 { + font-size: 14px; + } + + h4 { + font-size: 12px; + } + } + + md-grid-tile-header { + top: 0; + } + + md-grid-tile-footer { + bottom: 0; + } +} + + + diff --git a/modules/angular2_material/src/components/grid_list/grid_list.html b/modules/angular2_material/src/components/grid_list/grid_list.html new file mode 100644 index 0000000000..53765428cf --- /dev/null +++ b/modules/angular2_material/src/components/grid_list/grid_list.html @@ -0,0 +1,5 @@ +
+
+
+
+
diff --git a/modules/angular2_material/src/components/grid_list/grid_list.js b/modules/angular2_material/src/components/grid_list/grid_list.js new file mode 100644 index 0000000000..d8ab4d6b5b --- /dev/null +++ b/modules/angular2_material/src/components/grid_list/grid_list.js @@ -0,0 +1,253 @@ +import {Component, View, onAllChangesDone, Parent} from 'angular2/angular2'; +import {onDestroy, onChange} from 'angular2/src/core/annotations/annotations'; +import {ListWrapper} from 'angular2/src/facade/collection'; +import {CONST} from 'angular2/src/facade/lang'; +import {isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang'; +import {PropertySetter} from 'angular2/src/core/annotations/di'; + +// TODO(jelbourn): Set appropriate aria attributes for grid list elements. + +@Component({ + selector: 'md-grid-list', + properties: { + 'cols': 'cols', + 'gutterSize': 'gutter-size' + }, + lifecycle: [onChange] +}) +@View({ + templateUrl: 'angular2_material/src/components/grid_list/grid_list.html' +}) +export class MdGridList { + /** List of tiles that are being rendered. */ + tiles: Array; + + /** Number of columns being rendered. Can be either string or number */ + cols; + + /** Mode used to determine row heights. See RowHeightMode. */ + rowHeightMode: string; + + /** Fixed row height, as given by the user. Only used for 'fixed' mode. */ + fixedRowHeight: number; + + /** Ratio width:height given by user to determine row height. Only used for 'ratio' mode.*/ + rowHeightRatio: number; + + /** The amount of space between tiles. This will be something like '5px' or '2em'. */ + gutterSize: string; + + /** Array used to track the amount of space available. */ + spaceTracker: Array; + + constructor() { + this.tiles = []; + } + + onAllChangesDone() { + } + + onChange(_) { + if (!isPresent(this.spaceTracker)) { + if (isString(this.cols)) { + this.cols = NumberWrapper.parseIntAutoRadix(this.cols); + } + this.spaceTracker = ListWrapper.createFixedSize(this.cols); + ListWrapper.fill(this.spaceTracker, 0); + } + } + + /** + * Adds a tile to the grid-list. + * @param tile + */ + addTile(tile: MdGridTile) { + ListWrapper.push(this.tiles, tile); + } + + /** + * Removes a tile from the grid-list. + * @param tile + */ + removeTile(tile: MdGridTile) { + ListWrapper.remove(this.tiles, tile); + } + + /** + * Change handler invoked when bindings are resolved or when bindings have changed. + * Performs a layout. + */ + performLayout() { + //console.log('laying out!'); + } + + + /** + * Computes the amount of space a single 1x1 tile would take up (width or height). + * Used as a basis for other calculations. + * @param sizePercent Percent of the total grid-list space that one 1x1 tile would take up. + * @param gutterFraction Fraction of the gutter size taken up by one 1x1 tile. + * @return The size of a 1x1 tile as an expression that can be evaluated via CSS calc(). + */ + getBaseTileSize(sizePercent: number, gutterFraction: number): string { + // Take the base size percent (as would be if evenly dividing the size between cells), + // and then subtracting the size of one gutter. However, since there are no gutters on the + // edges, each tile only uses a fration (gutterShare = numGutters / numCells) of the gutter + // size. (Imagine having one gutter per tile, and then breaking up the extra gutter on the + // edge evenly among the cells). + return `${sizePercent}% - ( ${this.gutterSize} * ${gutterFraction} )`; + } + + + /** + * Gets The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value. + * @param offset Number of tiles that have already been rendered in the row/column. + * @param baseSize Base size of a 1x1 tile (as computed in getBaseTileSize). + * @return Position of the tile as a CSS calc() expression. + */ + getTilePosition(baseSize: string, offset: number): string { + // The position comes the size of a 1x1 tile plus gutter for each previous tile in the + // row/column (offset). + return `calc( (${baseSize} + ${this.gutterSize}) * ${offset} )`; + } + + + /** + * Gets the actual size of a tile, e.g., width or height, taking rowspan or colspan into account. + * @param baseSize Base size of a 1x1 tile (as computed in getBaseTileSize). + * @param span The tile's rowspan or colspan. + * @return Size of the tile as a CSS calc() expression. + */ + getTileSize(baseSize: string, span: number): string { + return `calc( (${baseSize} * ${span}) + (${span - 1} * ${this.gutterSize}) )`; + } + + + getTileStyle(tile: MdGridTile, rowIndex: number, colIndex: number): TileStyle { + // Percent of the available horizontal space that one column takes up. + var percentWidthPerTile = this.cols / 100; + + // Fraction of the gutter size that each column takes up. + // For example, if there are 5 columns, each column uses 4/5 = 0.8 times the gutter width. + var gutterWidthFractionPerTile = (this.cols - 1) / this.cols; + + // Base horizontal size of a column. + var baseTileWidth = getBaseTileSize(percentWidthPerTile, gutterWidthFractionPerTile); + + // The width and horizontal position of each tile is always calculated the same way, but the + // height and vertical position depends on the rowMode. + var tileStyle = new TileStyle(); + tileStyle.left = getTilePosition(baseTileWidth, colIndex); + tileStyle.width = getTileSize(baseTileWidth, tile.colspan); + + // TODO: make cases enums when we support enums + switch (this.rowHeightMode) { + case 'fixed': + // In fixed mode, simply use the given row height. + tileStyle.top = getTilePosition(this.fixedRowHeight, rowIndex); + tileStyle.height = getTileSize(this.fixedRowHeight, tile.rowspan); + break; + + case 'ratio': + var percentHeightPerTile = percentWidthPerTile / this.rowHeightRatio; + let baseTileHeight = getBaseTileSize(percentHeightPerTile, gutterWidthFractionPerTile); + + // Use paddingTop and marginTop to maintain the given aspect ratio, as + // a percentage-based value for these properties is applied to the *width* of the + // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties + tileStyle.marginTop = getTilePosition(baseTileHeight, rowIndex); + tileStyle.paddingTop = getTileSize(baseTileHeight, tile.rowspan); + break; + + case 'fit': + var percentHeightPerTile; + + break; + } + + return tileStyle; + } +} + +@Component({ + selector: 'md-grid-tile', + properties: { + 'rowspan': 'rowspan', + 'colspan': 'colspan' + }, + lifecycle: [onDestroy, onChange] +}) +@View({ + template: `
` +}) +export class MdGridTile { + gridList: MdGridList; + rowspan: number; + colspan: number; + heightSetter; + widthSetter; + topSetter; + leftSetter; + marginTopSetter; + paddingTopSetter; + + isRegisteredWithGridList: boolean; + + constructor( + @Parent() gridList: MdGridList, + @PropertySetter('style.height') heightSetter: Function, + @PropertySetter('style.width') widthSetter: Function, + @PropertySetter('style.top') topSetter: Function, + @PropertySetter('style.left') leftSetter: Function, + @PropertySetter('style.marginTop') marginTopSetter: Function, + @PropertySetter('style.paddingTop') paddingTopSetter: Function, + @PropertySetter('role') roleSetter: Function + ) { + this.gridList = gridList; + this.heightSetter = heightSetter; + this.widthSetter = widthSetter; + this.topSetter = topSetter; + this.leftSetter = leftSetter; + this.marginTopSetter = marginTopSetter; + this.paddingTopSetter = paddingTopSetter; + roleSetter('listitem'); + + // Tiles default to 1x1, but rowspan and colspan can be changed via binding. + this.rowspan = 1; + this.colspan = 1; + + // DEBUG + heightSetter(`${gridList.tiles.length * 100}px`); + } + + /** + * Change handler invoked when bindings are resolved or when bindings have changed. + * Notifies grid-list that a re-layout is required. + */ + onChange(_) { + //console.log(`grid-tile on-change ${this.gridList.tiles.indexOf(this)}`); + if (!this.isRegisteredWithGridList) { + this.gridList.addTile(this); + this.isRegisteredWithGridList = true; + } else { + this.gridList.performLayout(); + } + } + + /** + * Destructor function. Deregisters this tile from the containing grid-list. + */ + onDestroy() { + this.gridList.removeTile(this); + } +} + +/** Simple data structure for style values to be applied to a tile. */ +class TileStyle { + height: string; + width: string; + top: string; + left: string; + marginTop: string; + paddingTop: string; +} diff --git a/modules/angular2_material/src/components/input/input.js b/modules/angular2_material/src/components/input/input.js new file mode 100644 index 0000000000..17dbd3e1fc --- /dev/null +++ b/modules/angular2_material/src/components/input/input.js @@ -0,0 +1,18 @@ +import {Decorator} from 'angular2/angular2'; +import {PropertySetter} from 'angular2/src/core/annotations/di'; + +@Decorator({ + selector: 'md-input-container input' +}) +export class MdInput { + constructor() { + + } +} + + +@Decorator({ + selector: 'md-input-container' +}) +export class MdInputContainer { +} diff --git a/modules/angular2_material/src/components/input/input.scss b/modules/angular2_material/src/components/input/input.scss new file mode 100644 index 0000000000..a78e5be53a --- /dev/null +++ b/modules/angular2_material/src/components/input/input.scss @@ -0,0 +1,257 @@ +@import "../../core/style/variables"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +@mixin input-placeholder-color($color) { + &::-webkit-input-placeholder, + &::-moz-placeholder, /* Firefox 19+ */ + &:-moz-placeholder, /* Firefox 18- */ + &:-ms-input-placeholder { + color: $color; + } +} + +$input-container-padding: 2px !default; + +$input-label-default-offset: 24px !default; +$input-label-default-scale: 1.0 !default; +$input-label-float-offset: 4px !default; +$input-label-float-scale: 0.75 !default; + +$input-placeholder-offset: $input-label-default-offset !default; + +$input-border-width-default: 1px !default; +$input-border-width-focused: 2px !default; +$input-line-height: 26px !default; +$input-padding-top: 2px !default; + +$input-error-font-size: 12px !default; +$input-error-height: 24px !default; + +md-input-container { + box-sizing: border-box; + display: flex; + position: relative; + flex-direction: column; + + overflow-x: hidden; + padding: $input-container-padding; + padding-bottom: $input-container-padding + $input-error-height; + + > md-icon { + position: absolute; + top: 5px; + left: 2px; + + input { + margin-left: 28px * 2; + } + } + + textarea, + input[type="text"], + input[type="password"], + input[type="datetime"], + input[type="datetime-local"], + input[type="date"], + input[type="month"], + input[type="time"], + input[type="week"], + input[type="number"], + input[type="email"], + input[type="url"], + input[type="search"], + input[type="tel"], + input[type="color"] { + // Remove default appearance from all input / textarea. + // `appearance` is not supported in IE, but does IE apply any txt input styling? + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + } + + textarea { + resize: none; + overflow: hidden; + } + + textarea.md-input { + min-height: 2 * $input-line-height + $input-border-width-focused + $input-padding-top; + -ms-flex-preferred-size: auto; // IE10 + } + + label:not(.md-no-float), + .md-placeholder:not(.md-select-label) { + order: 1; + pointer-events: none; + -webkit-font-smoothing: antialiased; + padding-left: $input-container-padding; + z-index: 1; + transform: translate3d(0, $input-label-default-offset, 0) scale($input-label-default-scale); + transform-origin: left top; + transition: transform $swift-ease-out-timing-function 0.25s; + } + + .md-placeholder:not(.md-select-label) { + position: absolute; + top: 0; + opacity: 0; + transition-property: opacity, transform; + transform: translate3d(0, $input-placeholder-offset + $baseline-grid * 0.75, 0); + } + &.md-input-focused .md-placeholder { + opacity: 1; + transform: translate3d(0, $input-placeholder-offset, 0); + } + // Placeholder should immediately disappear when the user starts typing + &.md-input-has-value .md-placeholder { + transition: none; + opacity: 0; + } + + &:not( .md-input-has-value ) input:not( :focus ) { + color: transparent; + } + + + /* + * The .md-input class is added to the input/textarea + */ + .md-input { + flex: 1 1 auto; + order: 2; + display: block; + + background: none; + padding: $input-padding-top 2px $input-border-width-focused - $input-border-width-default; + border-width: 0 0 $input-border-width-default 0; + line-height: $input-line-height; + -ms-flex-preferred-size: $input-line-height; // IE10 + border-radius: 0; + + &:focus { + outline: none; + } + &:invalid { + outline: none; + box-shadow: none; + } + } + + .md-char-counter { + -webkit-font-smoothing: antialiased; + position: absolute; + font-size: $input-error-font-size; + line-height: $input-error-height; + bottom: $input-container-padding; + right: $input-container-padding; + + // TODO(jelbourn): animations here. + } + + &.md-input-focused, + &.md-input-has-value { + label:not(.md-no-float) { + transform: translate3d(0,$input-label-float-offset,0) scale($input-label-float-scale); + } + } + + // Use wide border in error state or in focused state + &.md-input-focused .md-input { + padding-bottom: 0; // Increase border width by 1px, decrease padding by 1 + border-width: 0 0 $input-border-width-focused 0; + } + + .md-input { + &[disabled], + [disabled] & { + background-position: 0 bottom; + // This background-size is coordinated with a linear-gradient set in input-theme.scss + // to create a dotted line under the input. + background-size: 3px 1px; + background-repeat: repeat-x; + } + } +} + + +// THEME + +md-input-container { + .md-input { + @include input-placeholder-color(md-color($md-foreground, hint-text)); + color: md-color($md-foreground, text); + border-color: md-color($md-foreground, divider); + // text-shadow: md-color($md-foreground, shadow); // what is this? + } + + > md-icon { + color: md-color($md-foreground, text); + } + + label, + .md-placeholder { + // text-shadow: md-color($md-foreground, shadow); // what is this? + color: md-color($md-foreground, hint-text); + } + + div[ng-messages] { + color: md-color($md-warn, 500) + } + + + &:not(.md-input-invalid) { + &.md-input-has-value { + label { + color: md-color($md-foreground, secondary-text); + } + } + &.md-input-focused { + .md-input { + border-color: md-color($md-primary, 500); + } + label { + color: md-color($md-primary, 500); + } + md-icon { + color: md-color($md-primary, 500); + } + &.md-accent { + .md-input { + border-color: md-color($md-accent, 500); + } + label { + color: md-color($md-accent, 500); + } + } + &.md-warn { + .md-input { + border-color: md-color($md-warn, 500); + } + label { + color: md-color($md-warn, 500); + } + } + } + } + &.md-input-invalid { + .md-input { + border-color: md-color($md-warn, 500); + } + label { + color: md-color($md-warn, 500); + } + .md-char-counter { + color: md-color($md-warn, 500); + } + } + .md-input { + &[disabled], + [disabled] & { + border-bottom-color: transparent; + color: md-color($md-foreground, disabled-text); + background-image: linear-gradient(to right, md-color($md-foreground, divider) 0%, md-color($md-foreground, divider) 33%, transparent 0%); + background-image: -ms-linear-gradient(left, transparent 0%, md-color($md-foreground, divider) 100%); + } + } +} diff --git a/modules/angular2_material/src/components/progress-circular/progress-circular.scss b/modules/angular2_material/src/components/progress-circular/progress-circular.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/angular2_material/src/components/progress-circular/progress_circular.html b/modules/angular2_material/src/components/progress-circular/progress_circular.html new file mode 100644 index 0000000000..162cb601e6 --- /dev/null +++ b/modules/angular2_material/src/components/progress-circular/progress_circular.html @@ -0,0 +1,11 @@ +
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/angular2_material/src/components/progress-circular/progress_circular.js b/modules/angular2_material/src/components/progress-circular/progress_circular.js new file mode 100644 index 0000000000..00e0abcb4a --- /dev/null +++ b/modules/angular2_material/src/components/progress-circular/progress_circular.js @@ -0,0 +1,14 @@ +import {Component, View} from 'angular2/angular2'; +import {isPresent} from 'angular2/src/facade/lang'; + + +@Component({ + selector: 'md-progress-circular' +}) +@View({ + templateUrl: 'angular2_material/src/components/progress-circular/progress_circular.html' +}) +export class MdProgressCircular { + constructor() { + } +} diff --git a/modules/angular2_material/src/components/progress-linear/progress_linear.html b/modules/angular2_material/src/components/progress-linear/progress_linear.html new file mode 100644 index 0000000000..e5034ef797 --- /dev/null +++ b/modules/angular2_material/src/components/progress-linear/progress_linear.html @@ -0,0 +1,9 @@ + + +
+
+
+
+
diff --git a/modules/angular2_material/src/components/progress-linear/progress_linear.js b/modules/angular2_material/src/components/progress-linear/progress_linear.js new file mode 100644 index 0000000000..e7803807e6 --- /dev/null +++ b/modules/angular2_material/src/components/progress-linear/progress_linear.js @@ -0,0 +1,98 @@ +import {Component, View, Attribute, PropertySetter, onChange} from 'angular2/angular2'; +import {isPresent, isBlank} from 'angular2/src/facade/lang'; +import {Math} from 'angular2/src/facade/math'; + +@Component({ + selector: 'md-progress-linear', + lifecycle: [onChange], + properties: { + 'value': 'value', + 'bufferValue': 'buffer-value' + } +}) +@View({ + templateUrl: 'angular2_material/src/components/progress-linear/progress_linear.html', + directives: [] +}) +export class MdProgressLinear { + /** Value for the primary bar. */ + value_: number; + + /** Value for the secondary bar. */ + bufferValue: number; + + /** The render mode for the progress bar. */ + mode: string; + + /** Attribute setter for aria-valuenow. */ + ariaValueNowSetter: Function; + + /** CSS `transform` property applied to the primary bar. */ + primaryBarTransform: string; + + /** CSS `transform` property applied to the secondary bar. */ + secondaryBarTransform: string; + + constructor( + @Attribute('md-mode') mode: string, + @PropertySetter('attr.role') roleSetter: Function, + @PropertySetter('attr.aria-valuemin') ariaValueMinSetter: Function, + @PropertySetter('attr.aria-valuemax') ariaValueMaxSetter: Function, + @PropertySetter('attr.aria-valuenow') ariaValueNowSetter: Function) { + this.ariaValueNowSetter = ariaValueNowSetter; + this.primaryBarTransform = ''; + this.secondaryBarTransform = ''; + + roleSetter('progressbar'); + ariaValueMinSetter('0'); + ariaValueMaxSetter('100'); + + this.mode = isPresent(mode) ? mode : Mode.DETERMINATE; + } + + get value() { + return this.value_; + } + + set value(v) { + if (isPresent(v)) { + this.value_ = MdProgressLinear.clamp(v); + this.ariaValueNowSetter(this.value_); + } + } + + onChange(_) { + // If the mode does not use a value, or if there is no value, do nothing. + if (this.mode == Mode['QUERY'] || this.mode == Mode['INDETERMINATE'] || isBlank(this.value)) { + return; + } + + this.primaryBarTransform = this.transformForValue(this.value); + + // The bufferValue is only used in buffer mode. + if (this.mode == Mode['BUFFER']) { + this.secondaryBarTransform = this.transformForValue(this.bufferValue); + } + } + + /** Gets the CSS `transform` property for a progress bar based on the given value (0 - 100). */ + transformForValue(value) { + // TODO(jelbourn): test perf gain of caching these, since there are only 101 values. + var scale = value / 100; + var translateX = (value - 100) / 2; + return `translateX(${translateX}%) scale(${scale}, 1)`; + } + + /** Clamps a value to be between 0 and 100. */ + static clamp(v) { + return Math.max(0, Math.min(100, v)); + } +} + +/** @enum {string} Progress-linear modes. */ +var Mode = { + 'DETERMINATE': 'determinate', + 'INDETERMINATE': 'indeterminate', + 'BUFFER': 'buffer', + 'QUERY': 'query' +}; diff --git a/modules/angular2_material/src/components/progress-linear/progress_linear.scss b/modules/angular2_material/src/components/progress-linear/progress_linear.scss new file mode 100644 index 0000000000..b332181d38 --- /dev/null +++ b/modules/angular2_material/src/components/progress-linear/progress_linear.scss @@ -0,0 +1,266 @@ +@import "../../core/style/variables"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$progress-linear-bar-height: 5px !default; + +md-progress-linear { + display: block; + width: 100%; + height: $progress-linear-bar-height; + + *, *:before { + box-sizing: border-box; + } + + .md-progress-linear-container { + overflow: hidden; + position: relative; + height: $progress-linear-bar-height; + top: $progress-linear-bar-height; + transform: translate(0, 5px) scale(1, 0); + transition: all .3s linear; + } + + .md-progress-linear-container.md-ready { + transform: translate(0, 0) scale(1, 1); + } + + .md-progress-linear-bar { + height: $progress-linear-bar-height; + position: absolute; + width: 100%; + } + + .md-progress-linear-bar1, .md-progress-linear-bar2 { + transition: all 0.2s linear; + } + + &[md-mode="determinate"] { + .md-progress-linear-bar1 { + display: none; + } + } + + &[md-mode="indeterminate"] { + .md-progress-linear-bar1 { + animation: indeterminate1 4s infinite linear; + } + + .md-progress-linear-bar2 { + animation: indeterminate2 4s infinite linear; + } + } + + &[md-mode="buffer"] { + .md-progress-linear-container { + background-color: transparent !important; + } + + .md-progress-linear-dashed:before { + content: ""; + display: block; + height: $progress-linear-bar-height; + width: 100%; + margin-top: 0px; + position: absolute; + background-color: transparent; + background-size: 10px 10px !important; + background-position: 0px -23px; + animation: buffer 3s infinite linear; + } + } + + &[md-mode="query"] { + .md-progress-linear-bar2 { + animation: query .8s infinite cubic-bezier(0.390, 0.575, 0.565, 1.000); + } + } +} + +@keyframes indeterminate1 { + 0% { + transform: translateX(-25%) scale(.5, 1); + } + 10% { + transform: translateX(25%) scale(.5, 1); + } + 19.99% { + transform: translateX(50%) scale(0, 1); + } + 20% { + transform: translateX(-37.5%) scale(.25, 1); + } + 30% { + transform: translateX(37.5%) scale(.25, 1); + } + 34.99% { + transform: translateX(50%) scale(0, 1); + } + 36.99% { + transform: translateX(50%) scale(0, 1); + } + 37% { + transform: translateX(-37.5%) scale(.25, 1); + } + 47% { + transform: translateX(20%) scale(.25, 1); + } + 52% { + transform: translateX(35%) scale(.05, 1); + } + 55% { + transform: translateX(35%) scale(.1, 1); + } + 58% { + transform: translateX(50%) scale(.1, 1); + } + 61.99% { + transform: translateX(50%) scale(0, 1); + } + 69.99% { + transform: translateX(50%) scale(0, 1); + } + 70% { + transform: translateX(-37.5%) scale(.25, 1); + } + 80% { + transform: translateX(20%) scale(.25, 1); + } + 85% { + transform: translateX(35%) scale(.05, 1); + } + 88% { + transform: translateX(35%) scale(.1, 1); + } + 91% { + transform: translateX(50%) scale(.1, 1); + } + 92.99% { + transform: translateX(50%) scale(0, 1); + } + 93% { + transform: translateX(-50%) scale(0, 1); + } + 100% { + transform: translateX(-25%) scale(.5, 1); + } +} + +@keyframes indeterminate2 { + 0% { + transform: translateX(-50%) scale(0, 1); + } + 25.99%{ + transform: translateX(-50%) scale(0, 1); + } + 28% { + transform: translateX(-37.5%) scale(.25, 1); + } + 38% { + transform: translateX(37.5%) scale(.25, 1); + } + 42.99% { + transform: translateX(50%) scale(0, 1); + } + 46.99% { + transform: translateX(50%) scale(0, 1); + } + 49.99% { + transform: translateX(50%) scale(0, 1); + } + 50% { + transform: translateX(-50%) scale(0, 1); + } + 60% { + transform: translateX(-25%) scale(.5, 1); + } + 70% { + transform: translateX(25%) scale(.5, 1); + } + 79.99% { + transform: translateX(50%) scale(0, 1); + } +} + +@keyframes query { + 0% { + opacity: 1; + transform: translateX(35%) scale(.3, 1); + } + 100% { + opacity: 0; + transform: translateX(-50%) scale(0, 1); + } +} + +@keyframes buffer { + 0% { + opacity: 1; + background-position: 0px -23px; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + background-position: -200px -23px; + } +} + + +// THEME + +md-progress-linear { + .md-progress-linear-container { + background-color: md-color($md-primary, 100); + } + + .md-progress-linear-bar { + background-color: md-color($md-primary); + } + + &.md-warn .md-progress-linear-container { + background-color: md-color($md-warn, 100); + } + + &.md-warn .md-progress-linear-bar { + background-color: md-color($md-warn); + } + + &.md-accent .md-progress-linear-container { + background-color: md-color($md-accent, 100); + } + + &.md-accent .md-progress-linear-bar { + background-color: md-color($md-accent); + } + + &[md-mode=buffer] { + &.md-primary { + .md-progress-linear-bar1 { + background-color: md-color($md-primary, 100); + } + .md-progress-linear-dashed:before { + background: radial-gradient(md-color($md-primary, 100) 0%, md-color($md-primary, 100) 16%, transparent 42%); + } + } + &.md-warn { + .md-progress-linear-bar1 { + background-color: md-color($md-warn, 100); + } + .md-progress-linear-dashed:before { + background: radial-gradient(md-color($md-warn, 100) 0%, md-color($md-warn, 100) 16%, transparent 42%); + } + } + &.md-accent { + .md-progress-linear-bar1 { + background-color: md-color($md-accent, 100); + } + .md-progress-linear-dashed:before { + background: radial-gradient(md-color($md-accent, 100) 0%, md-color($md-accent, 100) 16%, transparent 42%); + } + } + } +} diff --git a/modules/angular2_material/src/components/radio/radio-button.scss b/modules/angular2_material/src/components/radio/radio-button.scss new file mode 100644 index 0000000000..cdb8ee6972 --- /dev/null +++ b/modules/angular2_material/src/components/radio/radio-button.scss @@ -0,0 +1,136 @@ +@import "../../core/style/variables"; +@import "../../core/style/shadows"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$radio-width: 16px !default; +$radio-height: $radio-width !default; + +md-radio-button { + display: block; + margin: 15px; + white-space: nowrap; + cursor: pointer; +} + +md-radio-group { + border: 1px dotted transparent; + display: block; + outline: none; +} + +.md-radio-container { + box-sizing: border-box; + position: relative; + top: 4px; + display: inline-block; + width: $radio-width; + height: $radio-width; + cursor: pointer; +} + +.md-radio-off { + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + width: $radio-width; + height: $radio-width; + border: solid 2px; + border-radius: 50%; + transition: border-color ease 0.28s; +} + +.md-radio-on { + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + width: $radio-width; + height: $radio-width; + border-radius: 50%; + transition: transform ease 0.28s; + transform: scale(0); + + .md-radio-checked & { + transform: scale(0.55); + } +} + +// This is the style applied to the content (included via ). If we could rely on shadow +// DOM always being present, this would use the ::content psuedo-class. +.md-radio-label { + position: relative; + display: inline-block; + margin-left: 10px; + margin-right: 10px; + vertical-align: middle; + white-space: normal; + // pointer-events: none; ??? + width: auto; +} + + +// THEME + +.md-radio-off { + border-color: md-color($md-foreground, icon); +} + +.md-radio-on { + background-color: md-color($md-accent, 0.87); +} + +.md-radio-checked .md-radio-off { + border-color: md-color($md-accent, 0.87); +} + +// TODO +.md-radio-checked .md-ink-ripple { } +.md-radio-container .md-ripple { } +p + +md-radio-group:not([disabled]) md-radio:not([disabled]) { + + &.md-primary { + .md-radio-on { + background-color: md-color($md-primary, 0.87); + } + + &.md-radio-checked .md-radio-off { + border-color: md-color($md-primary, 0.87); + } + + &.md-radio-checked .md-ink-ripple { + color: md-color($md-primary, 0.87); + } + + .md-radio-container .md-ripple { + color: md-color($md-primary, 600); + } + } + + &.md-warn { + .md-radio-on { + background-color: md-color($md-warn, 0.87); + } + &.md-radio-checked .md-radio-off { + border-color: md-color($md-warn, 0.87); + } + &.md-radio-checked .md-ink-ripple { + color: md-color($md-warn, 0.87); + } + .md-radio-container .md-ripple { + color: md-color($md-warn, 600); + } + } +} + +md-radio-button[disabled], +md-radio-group[disabled] md-radio-button { + .md-radio-container .md-radio-off, + .md-radio-container .md-radio-on { + border-color: md-color($md-foreground, disabled); + } +} diff --git a/modules/angular2_material/src/components/radio/radio-group.scss b/modules/angular2_material/src/components/radio/radio-group.scss new file mode 100644 index 0000000000..9740f0f070 --- /dev/null +++ b/modules/angular2_material/src/components/radio/radio-group.scss @@ -0,0 +1,5 @@ +md-radio-group { + border: 1px dotted transparent; + display: block; + outline: none; +} diff --git a/modules/angular2_material/src/components/radio/radio_button.html b/modules/angular2_material/src/components/radio/radio_button.html new file mode 100644 index 0000000000..7406388266 --- /dev/null +++ b/modules/angular2_material/src/components/radio/radio_button.html @@ -0,0 +1,18 @@ + + + + + diff --git a/modules/angular2_material/src/components/radio/radio_button.js b/modules/angular2_material/src/components/radio/radio_button.js new file mode 100644 index 0000000000..00e145fc92 --- /dev/null +++ b/modules/angular2_material/src/components/radio/radio_button.js @@ -0,0 +1,341 @@ +import {Component, View, Parent, Ancestor, Attribute, PropertySetter, + EventEmitter} from 'angular2/angular2'; +import {Optional} from 'angular2/src/di/annotations'; +import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher' +import {MdTheme} from 'angular2_material/src/core/theme' +import {onChange} from 'angular2/src/core/annotations/annotations'; +import {isPresent, StringWrapper} from 'angular2/src/facade/lang'; +// import {KeyCodes} from 'angular2_material/src/core/constants' +import {Math} from 'angular2/src/facade/math'; +import {ListWrapper} from 'angular2/src/facade/collection'; + +// TODO(jelbourn): Behaviors to test +// Disabled radio don't select +// Disabled radios don't propagate click event +// Radios are disabled by parent group +// Radios set default tab index iff not in parent group +// Radios are unique-select +// Radio updates parent group's value +// Change to parent group's value updates the selected child radio +// Radio name is pulled on parent group +// Radio group changes on arrow keys +// Radio group skips disabled radios on arrow keys + +var _uniqueIdCounter:number = 0; + +@Component({ + selector: 'md-radio-button', + lifecycle: [onChange], + properties: { + 'id': 'id', + 'name': 'name', + 'value': 'value', + 'checked': 'checked', + 'disabled': 'disabled' + }, + hostListeners: { + 'keydown': 'onKeydown($event)' + } +}) +@View({ + templateUrl: 'angular2_material/src/components/radio/radio_button.html', + directives: [] +}) +export class MdRadioButton { + /** Whether this radio is checked. */ + checked_: boolean; + + /** Whether the radio is disabled. */ + disabled_: boolean; + + /** The unique ID for the radio button. */ + id: string; + + /** Analog to HTML 'name' attribute used to group radios for unique selection. */ + name: string; + + /** Value assigned to this radio. Used to assign the value to the parent MdRadioGroup. */ + value: any; + + /** The parent radio group. May or may not be present. */ + radioGroup: MdRadioGroup; + + /** Dispatcher for coordinating radio unique-selection by name. */ + radioDispatcher: MdRadioDispatcher; + + /** Setter for `aria-checked` attribute. */ + ariaCheckedSetter: Function; + + /** Setter for `aria-disabled` attribute. */ + ariaDisabledSetter: Function; + + constructor( + @Optional() @Parent() radioGroup: MdRadioGroup, + @Attribute('id') id: string, + @Attribute('tabindex') tabindex: string, + @PropertySetter('id') idSetter: Function, + @PropertySetter('tabindex') tabindexSetter: Function, + @PropertySetter('attr.role') roleSetter: Function, + @PropertySetter('attr.aria-checked') ariaCheckedSetter: Function, + @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function, + radioDispatcher: MdRadioDispatcher) { + // Assertions. Ideally these should be stripped out by the compiler. + // TODO(jelbourn): Assert that there's no name binding AND a parent radio group. + + this.radioGroup = radioGroup; + this.radioDispatcher = radioDispatcher; + this.ariaCheckedSetter = ariaCheckedSetter; + this.ariaDisabledSetter = ariaDisabledSetter; + this.value = null; + + roleSetter('radio'); + this.checked = false; + + this.id = isPresent(id) ? id : `md-radio-${_uniqueIdCounter++}`; + idSetter(this.id); + + // Whenever a radio button with the same name is checked, uncheck this radio button. + radioDispatcher.listen((name) => { + if (name == this.name) { + this.checked = false; + } + }); + + // When this radio-button is inside of a radio-group, the group determines the name. + if (isPresent(radioGroup)) { + this.name = radioGroup.getName(); + this.radioGroup.register(this); + } + + // If the user has not set a tabindex, default to zero (in the normal document flow). + if (!isPresent(radioGroup)) { + tabindexSetter(isPresent(tabindex) ? tabindex : '0'); + } + } + + /** Change handler invoked when bindings are resolved or when bindings have changed. */ + onChange(_) { + if (isPresent(this.radioGroup)) { + this.name = this.radioGroup.getName(); + } + } + + /** Whether this radio button is disabled, taking the parent group into account. */ + isDisabled(): boolean { + // Here, this.disabled may be true/false as the result of a binding, may be the empty string + // if the user just adds a `disabled` attribute with no value, or may be absent completely. + // TODO(jelbourn): If someone sets `disabled="disabled"`, will this work in dart? + return this.disabled || + (isPresent(this.disabled) && StringWrapper.equals(this.disabled, '')) || + (isPresent(this.radioGroup) && this.radioGroup.disabled); + } + + get checked() { + return this.checked_; + } + + set checked(value) { + this.checked_ = value; + this.ariaCheckedSetter(value); + } + + get disabled() { + return this.disabled_; + } + + set disabled(value) { + this.disabled_ = isPresent(value) && value !== false; + this.ariaDisabledSetter(this.disabled_); + } + + /** Select this radio button. */ + select(event: Event) { + if (this.isDisabled()) { + event.stopPropagation(); + return; + } + + // Notifiy all radio buttons with the same name to un-check. + this.radioDispatcher.notify(this.name); + + this.checked = true; + + if (this.radioGroup) { + this.radioGroup.updateValue(this.value, this.id); + } + } + + /** Handles pressing the space key to select this focused radio button. */ + onKeydown(event: KeyboardEvent) { + if (event.keyCode == KeyCodes.SPACE) { + event.preventDefault(); + this.select(event); + } + } +} + +@Component({ + selector: 'md-radio-group', + lifecycle: [onChange], + properties: { + 'disabled': 'disabled', + 'value': 'value' + }, + hostListeners: { + 'keydown': 'onKeydown($event)' + } +}) +@View({ + templateUrl: 'angular2_material/src/components/radio/radio_group.html' +}) +export class MdRadioGroup { + /** The selected value for the radio group. The value comes from the options. */ + value: any; + + /** The HTML name attribute applied to radio buttons in this group. */ + name_: string; + + /** Dispatcher for coordinating radio unique-selection by name. */ + radioDispatcher: MdRadioDispatcher; + + /** List of child radio buttons. */ + radios_: List; + + changeEmitter: Function; + + ariaActiveDescendantSetter: Function; + + ariaDisabledSetter: Function; + + disabled_: boolean; + + /** The ID of the selected radio button. */ + selectedRadioId: string; + + constructor( + @Attribute('tabindex') tabindex: string, + @Attribute('disabled') disabled: string, + @PropertySetter('tabindex') tabindexSetter: Function, + @PropertySetter('attr.role') roleSetter: Function, + @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function, + @PropertySetter('attr.aria-activedescendant') ariaActiveDescendantSetter: Function, + @EventEmitter('change') changeEmitter: Function, + radioDispatcher: MdRadioDispatcher) { + this.name_ = `md-radio-group-${_uniqueIdCounter++}`; + this.radios_ = []; + this.changeEmitter = changeEmitter; + this.ariaActiveDescendantSetter = ariaActiveDescendantSetter; + this.ariaDisabledSetter = ariaDisabledSetter; + this.radioDispatcher = radioDispatcher; + this.selectedRadioId = ''; + this.disabled_ = false; + + roleSetter('radiogroup'); + + // The simple presence of the `disabled` attribute dictates disabled state. + this.disabled = isPresent(disabled); + + // If the user has not set a tabindex, default to zero (in the normal document flow). + tabindexSetter(isPresent(tabindex) ? tabindex : '0'); + } + + /** Gets the name of this group, as to be applied in the HTML 'name' attribute. */ + getName(): string { + return this.name_; + } + + get disabled() { + return this.disabled_; + } + + set disabled(value) { + this.disabled_ = isPresent(value) && value !== false; + this.ariaDisabledSetter(this.disabled_); + } + + /** Change handler invoked when bindings are resolved or when bindings have changed. */ + onChange(_) { + // If the component has a disabled attribute with no value, it will set disabled = ''. + this.disabled = isPresent(this.disabled) && this.disabled !== false; + + // If the value of this radio-group has been set or changed, we have to look through the + // child radio buttons and select the one that has a corresponding value (if any). + if (isPresent(this.value) && this.value != '') { + this.radioDispatcher.notify(this.name_); + ListWrapper.forEach(this.radios_, (radio) => { + if (radio.value == this.value) { + radio.checked = true; + this.selectedRadioId = radio.id; + this.ariaActiveDescendantSetter(radio.id); + } + }); + } + } + + /** Update the value of this radio group from a child md-radio being selected. */ + updateValue(value: any, id: string) { + this.value = value; + this.selectedRadioId = id; + this.ariaActiveDescendantSetter(id); + this.changeEmitter(); + } + + /** Registers a child radio button with this group. */ + register(radio: MdRadioButton) { + ListWrapper.push(this.radios_, radio); + } + + /** Handles up and down arrow key presses to change the selected child radio. */ + onKeydown(event: KeyboardEvent) { + if (this.disabled) { + return; + } + + switch (event.keyCode) { + //case KeyCodes.UP: + case 38: + this.stepSelectedRadio(-1); + event.preventDefault(); + break; + //case KeyCodes.DOWN: + case 40: + this.stepSelectedRadio(1); + event.preventDefault(); + break; + } + } + + // TODO(jelbourn): Replace this with a findIndex method in the collections facade. + getSelectedRadioIndex(): number { + for (var i = 0; i < this.radios_.length; i++) { + if (this.radios_[i].id == this.selectedRadioId) { + return i; + } + } + + return -1; + } + + /** Steps the selected radio based on the given step value (usually either +1 or -1). */ + stepSelectedRadio(step) { + var index = this.getSelectedRadioIndex() + step; + if (index < 0 || index >= this.radios_.length) { + return; + } + + var radio = this.radios_[index]; + + // If the next radio is line is disabled, skip it (maintaining direction). + if (radio.disabled) { + this.stepSelectedRadio(step + (step < 0 ? -1 : 1)); + return; + } + + this.radioDispatcher.notify(this.name_); + radio.checked = true; + + this.value = radio.value; + this.selectedRadioId = radio.id; + this.ariaActiveDescendantSetter(radio.id); + } +} diff --git a/modules/angular2_material/src/components/radio/radio_dispatcher.js b/modules/angular2_material/src/components/radio/radio_dispatcher.js new file mode 100644 index 0000000000..0f77ef3500 --- /dev/null +++ b/modules/angular2_material/src/components/radio/radio_dispatcher.js @@ -0,0 +1,24 @@ +import {ListWrapper} from 'angular2/src/facade/collection'; + +/** + * Class for radio buttons to coordinate unique selection based on name. + * Indended to be consumed as an Angular service. + */ +export class MdRadioDispatcher { + // TODO(jelbourn): Change this to TypeScript syntax when supported. + listeners_: Array; + + constructor() { + this.listeners_ = []; + } + + /** Notify other nadio buttons that selection for the given name has been set. */ + notify(name: string) { + ListWrapper.forEach(this.listeners_, (f) => f(name)); + } + + /** Listen for future changes to radio button selection. */ + listen(listener) { + ListWrapper.push(this.listeners_, listener); + } +} diff --git a/modules/angular2_material/src/components/radio/radio_group.html b/modules/angular2_material/src/components/radio/radio_group.html new file mode 100644 index 0000000000..eec67d1538 --- /dev/null +++ b/modules/angular2_material/src/components/radio/radio_group.html @@ -0,0 +1,2 @@ + + diff --git a/modules/angular2_material/src/components/switcher/switch.html b/modules/angular2_material/src/components/switcher/switch.html new file mode 100644 index 0000000000..a2022b915f --- /dev/null +++ b/modules/angular2_material/src/components/switcher/switch.html @@ -0,0 +1,11 @@ + + +
+
+
+
+
+
+
+
+
diff --git a/modules/angular2_material/src/components/switcher/switch.js b/modules/angular2_material/src/components/switcher/switch.js new file mode 100644 index 0000000000..96f01d8ab8 --- /dev/null +++ b/modules/angular2_material/src/components/switcher/switch.js @@ -0,0 +1,80 @@ +import {Component, View, Attribute, PropertySetter} from 'angular2/angular2'; +import {isPresent} from 'angular2/src/facade/lang'; +import {KeyCodes} from 'angular2_material/src/core/constants' + +// TODO(jelbourn): without gesture support, this is identical to MdCheckbox. + +@Component({ + selector: 'md-switch', + properties: { + 'checked': 'checked', + 'disabled': 'disabled' + }, + hostListeners: { + 'keydown': 'onKeydown($event)' + } +}) +@View({ + templateUrl: 'angular2_material/src/components/switcher/switch.html', + directives: [] +}) +export class MdSwitch { + /** Whether this switch is checked. */ + checked_: boolean; + + /** Setter for `aria-checked` attribute. */ + ariaCheckedSetter: Function; + + /** Setter for `aria-disabled` attribute. */ + ariaDisabledSetter: Function; + + constructor( + @Attribute('tabindex') tabindex: string, + @PropertySetter('tabindex') tabindexSetter: Function, + @PropertySetter('attr.role') roleSetter: Function, + @PropertySetter('attr.aria-checked') ariaCheckedSetter: Function, + @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function) { + this.ariaCheckedSetter = ariaCheckedSetter; + this.ariaDisabledSetter = ariaDisabledSetter; + + roleSetter('checkbox'); + this.checked = false; + tabindexSetter(isPresent(tabindex) ? tabindex : '0'); + } + + get checked() { + return this.checked_; + } + + set checked(value) { + this.checked_ = value; + this.ariaCheckedSetter(value); + } + + get disabled() { + return this.disabled_; + } + + set disabled(value) { + this.disabled_ = isPresent(value) && value !== false; + this.ariaDisabledSetter(this.disabled_); + } + + onKeydown(event: KeyboardEvent) { + if (event.keyCode == KeyCodes.SPACE) { + event.preventDefault(); + this.toggle(event); + } + } + + toggle(event) { + if (this.disabled) { + event.stopPropagation(); + return; + } + + this.checked = !this.checked; + this.ariaCheckedSetter(this.checked); + } +} + diff --git a/modules/angular2_material/src/components/switcher/switch.scss b/modules/angular2_material/src/components/switcher/switch.scss new file mode 100644 index 0000000000..3fb12f7d7a --- /dev/null +++ b/modules/angular2_material/src/components/switcher/switch.scss @@ -0,0 +1,172 @@ +@import "../../core/style/variables"; + +// TODO(jelbourn): This goes away. +@import "../../core/style/default-theme"; + +$switch-width: 36px !default; +$switch-height: $baseline-grid * 3 !default; +$switch-bar-height: 14px !default; +$switch-thumb-size: 20px !default; + +md-switch { + display: flex; + align-items: center; + + margin: 15px; + white-space: nowrap; + cursor: pointer; + outline: none; + user-select: none; + + * { + box-sizing: border-box; + } + + .md-switch-container { + cursor: grab; + width: $switch-width; + height: $switch-height; + position: relative; + user-select: none; + margin-right: 8px; + } + + // If the user moves his mouse off the switch, stil display grabbing cursor + &:not([disabled]) { + .md-switch-dragging, + &.md-switch-dragging .md-switch-container { + cursor: grabbing; + } + } + + .md-switch-label { + border: 0 transparent; + } + + .md-switch-bar { + left: 1px; + width: $switch-width - 2px; + top: $switch-height / 2 - $switch-bar-height / 2; + height: $switch-bar-height; + border-radius: 8px; + position: absolute; + } + + .md-switch-thumb-container { + top: $switch-height / 2 - $switch-thumb-size / 2; + left: 0; + width: $switch-width - $switch-thumb-size; + position: absolute; + transform: translate3d(0,0,0); + z-index: 1; + } + + &[aria-checked="true"] .md-switch-thumb-container { + transform: translate3d(100%, 0, 0); + } + + .md-switch-thumb { + position: absolute; + margin: 0; + left: 0; + top: 0; + outline: none; + height: $switch-thumb-size; + width: $switch-thumb-size; + border-radius: 50%; + box-shadow: $whiteframe-shadow-z1; + + // todo + .md-ripple-container { + position: absolute; + display: block; + width: auto; + height: auto; + left: -$switch-thumb-size; + top: -$switch-thumb-size; + right: -$switch-thumb-size; + bottom: -$switch-thumb-size; + } + } + + &:not(.md-switch-dragging) { + .md-switch-bar, + .md-switch-thumb-container, + .md-switch-thumb { + transition: $swift-ease-in-out; + transition-property: transform, background-color; + } + .md-switch-bar, + .md-switch-thumb { + transition-delay: 0.05s; + } + } + +} + +// TODO(jelbourn): Why are these not defined in terms of the theme? +@media screen and (-ms-high-contrast: active) { + md-switch .md-switch-bar { + background-color: #666; + } + md-switch[aria-checked="true"] .md-switch-bar { + background-color: #9E9E9E; + } + md-switch.md-default-theme .md-switch-thumb { + background-color: #fff; + } +} + + +// THEME + +md-switch { + .md-switch-thumb { + background-color: md-color($md-background, 50); + } + .md-switch-bar { + background-color: md-color($md-background, 500); + } + + &[aria-checked="true"] { + .md-switch-thumb { + background-color: md-color($md-accent); + } + .md-switch-bar { + background-color: md-color($md-accent, 0.5); + } + + &.md-primary { + .md-switch-thumb { + background-color: md-color($md-primary); + } + .md-switch-bar { + background-color: md-color($md-primary, 0.5); + } + } + + &.md-warn { + .md-switch-thumb { + background-color: md-color($md-warn); + } + .md-switch-bar { + background-color: md-color($md-warn, 0.5); + } + } + } + + &[disabled] { + .md-switch-thumb { + background-color: md-color($md-background, 400); + } + .md-switch-bar { + background-color: md-color($md-foreground, divider); + } + } + + &:focus { + .md-switch-label:not(:empty) { + border: 1px dotted md-color($md-foreground, text); + } + } +} diff --git a/modules/angular2_material/src/core/constants.js b/modules/angular2_material/src/core/constants.js new file mode 100644 index 0000000000..4ff33039ad --- /dev/null +++ b/modules/angular2_material/src/core/constants.js @@ -0,0 +1,8 @@ + +// TODO: this is not how TS&Dart enums are defined and it definitely won't +// work in Dart. Switch to proper enums when we support them. +//export var KeyCodes = { +// SPACE: 32, +// UP: 38, +// DOWN: 40 +//}; diff --git a/modules/angular2_material/src/core/style/default-theme.scss b/modules/angular2_material/src/core/style/default-theme.scss new file mode 100644 index 0000000000..72da087ea2 --- /dev/null +++ b/modules/angular2_material/src/core/style/default-theme.scss @@ -0,0 +1,13 @@ +@import 'theme-functions'; +@import 'palette'; + + +// Person creating a theme writes variables like this: +$md-is-dark-theme: false; + + +$md-primary: md-palette($md-indigo, 500, 100, 700, $md-contrast-palettes); +$md-accent: md-palette($md-red, A200, A100, A400, $md-contrast-palettes); +$md-background: md-palette($md-grey, 500, 300, 600, $md-contrast-palettes); +$md-warn: md-palette($md-red, 500, 300, 800, $md-contrast-palettes); +$md-foreground: if($md-is-dark-theme, $md-dark-theme-foreground, $md-light-theme-foreground); diff --git a/modules/angular2_material/src/core/style/palette.scss b/modules/angular2_material/src/core/style/palette.scss new file mode 100644 index 0000000000..2f5bf3e9c6 --- /dev/null +++ b/modules/angular2_material/src/core/style/palette.scss @@ -0,0 +1,666 @@ +// Core color palettes. +$md-red: ( + 50: #ffebee, + 100: #ffcdd2, + 200: #ef9a9a, + 300: #e57373, + 400: #ef5350, + 500: #f44336, + 600: #e53935, + 700: #d32f2f, + 800: #c62828, + 900: #b71c1c, + A100: #ff8a80, + A200: #ff5252, + A400: #ff1744, + A700: #d50000, +); + +$md-pink: ( + 50: #fce4ec, + 100: #f8bbd0, + 200: #f48fb1, + 300: #f06292, + 400: #ec407a, + 500: #e91e63, + 600: #d81b60, + 700: #c2185b, + 800: #ad1457, + 900: #880e4f, + A100: #ff80ab, + A200: #ff4081, + A400: #f50057, + A700: #c51162, +); + +$md-purple: ( + 50: #f3e5f5, + 100: #e1bee7, + 200: #ce93d8, + 300: #ba68c8, + 400: #ab47bc, + 500: #9c27b0, + 600: #8e24aa, + 700: #7b1fa2, + 800: #6a1b9a, + 900: #4a148c, + A100: #ea80fc, + A200: #e040fb, + A400: #d500f9, + A700: #aa00ff, +); + +$md-deep-purple: ( + 50: #ede7f6, + 100: #d1c4e9, + 200: #b39ddb, + 300: #9575cd, + 400: #7e57c2, + 500: #673ab7, + 600: #5e35b1, + 700: #512da8, + 800: #4527a0, + 900: #311b92, + A100: #b388ff, + A200: #7c4dff, + A400: #651fff, + A700: #6200ea, +); + +$md-indigo: ( + 50: #e8eaf6, + 100: #c5cae9, + 200: #9fa8da, + 300: #7986cb, + 400: #5c6bc0, + 500: #3f51b5, + 600: #3949ab, + 700: #303f9f, + 800: #283593, + 900: #1a237e, + A100: #8c9eff, + A200: #536dfe, + A400: #3d5afe, + A700: #304ffe, +); + +$md-blue: ( + 50: #e3f2fd, + 100: #bbdefb, + 200: #90caf9, + 300: #64b5f6, + 400: #42a5f5, + 500: #2196f3, + 600: #1e88e5, + 700: #1976d2, + 800: #1565c0, + 900: #0d47a1, + A100: #82b1ff, + A200: #448aff, + A400: #2979ff, + A700: #2962ff, +); + +$md-light-blue: ( + 50: #e1f5fe, + 100: #b3e5fc, + 200: #81d4fa, + 300: #4fc3f7, + 400: #29b6f6, + 500: #03a9f4, + 600: #039be5, + 700: #0288d1, + 800: #0277bd, + 900: #01579b, + A100: #80d8ff, + A200: #40c4ff, + A400: #00b0ff, + A700: #0091ea, +); + +$md-cyan: ( + 50: #e0f7fa, + 100: #b2ebf2, + 200: #80deea, + 300: #4dd0e1, + 400: #26c6da, + 500: #00bcd4, + 600: #00acc1, + 700: #0097a7, + 800: #00838f, + 900: #006064, + A100: #84ffff, + A200: #18ffff, + A400: #00e5ff, + A700: #00b8d4, +); + +$md-teal: ( + 50: #e0f2f1, + 100: #b2dfdb, + 200: #80cbc4, + 300: #4db6ac, + 400: #26a69a, + 500: #009688, + 600: #00897b, + 700: #00796b, + 800: #00695c, + 900: #004d40, + A100: #a7ffeb, + A200: #64ffda, + A400: #1de9b6, + A700: #00bfa5, +); + +$md-green: ( + 50: #e8f5e9, + 100: #c8e6c9, + 200: #a5d6a7, + 300: #81c784, + 400: #66bb6a, + 500: #4caf50, + 600: #43a047, + 700: #388e3c, + 800: #2e7d32, + 900: #1b5e20, + A100: #b9f6ca, + A200: #69f0ae, + A400: #00e676, + A700: #00c853, +); + +$md-light-green: ( + 50: #f1f8e9, + 100: #dcedc8, + 200: #c5e1a5, + 300: #aed581, + 400: #9ccc65, + 500: #8bc34a, + 600: #7cb342, + 700: #689f38, + 800: #558b2f, + 900: #33691e, + A100: #ccff90, + A200: #b2ff59, + A400: #76ff03, + A700: #64dd17, +); + +$md-lime: ( + 50: #f9fbe7, + 100: #f0f4c3, + 200: #e6ee9c, + 300: #dce775, + 400: #d4e157, + 500: #cddc39, + 600: #c0ca33, + 700: #afb42b, + 800: #9e9d24, + 900: #827717, + A100: #f4ff81, + A200: #eeff41, + A400: #c6ff00, + A700: #aeea00, +); + +$md-yellow: ( + 50: #fffde7, + 100: #fff9c4, + 200: #fff59d, + 300: #fff176, + 400: #ffee58, + 500: #ffeb3b, + 600: #fdd835, + 700: #fbc02d, + 800: #f9a825, + 900: #f57f17, + A100: #ffff8d, + A200: #ffff00, + A400: #ffea00, + A700: #ffd600, +); + +$md-amber: ( + 50: #fff8e1, + 100: #ffecb3, + 200: #ffe082, + 300: #ffd54f, + 400: #ffca28, + 500: #ffc107, + 600: #ffb300, + 700: #ffa000, + 800: #ff8f00, + 900: #ff6f00, + A100: #ffe57f, + A200: #ffd740, + A400: #ffc400, + A700: #ffab00, +); + +$md-orange: ( + 50: #fff3e0, + 100: #ffe0b2, + 200: #ffcc80, + 300: #ffb74d, + 400: #ffa726, + 500: #ff9800, + 600: #fb8c00, + 700: #f57c00, + 800: #ef6c00, + 900: #e65100, + A100: #ffd180, + A200: #ffab40, + A400: #ff9100, + A700: #ff6d00, +); + +$md-deep-orange: ( + 50: #fbe9e7, + 100: #ffccbc, + 200: #ffab91, + 300: #ff8a65, + 400: #ff7043, + 500: #ff5722, + 600: #f4511e, + 700: #e64a19, + 800: #d84315, + 900: #bf360c, + A100: #ff9e80, + A200: #ff6e40, + A400: #ff3d00, + A700: #dd2c00, +); + +$md-brown: ( + 50: #efebe9, + 100: #d7ccc8, + 200: #bcaaa4, + 300: #a1887f, + 400: #8d6e63, + 500: #795548, + 600: #6d4c41, + 700: #5d4037, + 800: #4e342e, + 900: #3e2723, + A100: #d7ccc8, + A200: #bcaaa4, + A400: #8d6e63, + A700: #5d4037, +); + +$md-grey: ( + 0: #ffffff, + 50: #fafafa, + 100: #f5f5f5, + 200: #eeeeee, + 300: #e0e0e0, + 400: #bdbdbd, + 500: #9e9e9e, + 600: #757575, + 700: #616161, + 800: #424242, + 900: #212121, + 1000: #000000, + A100: #ffffff, + A200: #eeeeee, + A400: #bdbdbd, + A700: #616161, +); + +$md-blue-grey: ( + 50: #eceff1, + 100: #cfd8dc, + 200: #b0bec5, + 300: #90a4ae, + 400: #78909c, + 500: #607d8b, + 600: #546e7a, + 700: #455a64, + 800: #37474f, + 900: #263238, + A100: #cfd8dc, + A200: #b0bec5, + A400: #78909c, + A700: #455a64, +); + + +$md-light-theme-foreground: ( + divider: rgba(black, 0.12), + dividers: rgba(black, 0.12), + disabled: rgba(black, 0.26), + disabled-text: rgba(black, 0.26), + hint-text: rgba(black, 0.26), + secondary-text: rgba(black, 0.54), + icon: rgba(black, 0.54), + icons: rgba(black, 0.54), + text: rgba(black, 0.87) +); + +$md-dark-theme-foreground: ( + divider: rgba(white, 0.12), + dividers: rgba(white, 0.12), + disabled: rgba(white, 0.30), + disabled-text: rgba(white, 0.30), + hint-text: rgba(white, 0.30), + secondary-text: rgba(white, 0.70), + icon: white, + icons: white, + text: white +); + + +// Contrast colors. These are hard-coded because it is too difficult to calculate them. +// These contrast colors are pulled from the public Material Design spec swatches. While the +// contrast colors in the spec are not perscriptive, we will use them for convenience. +$black-87-opacity: rgba(black, 0.870588); +$white-87-opacity: rgba(white, 0.870588); + +$md-contrast-palettes: ( + $md-red: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-pink: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-purple: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-deep-purple: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: $white-87-opacity, + A700: $white-87-opacity, + ), + $md-indigo: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: $white-87-opacity, + ), + $md-blue: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: white, + A400: white, + A700: white, + ), + $md-light-blue: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: white, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: white, + ), + $md-cyan: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: white, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-teal: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-green: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-light-green: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: white, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-lime: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: $black-87-opacity, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-yellow: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: $black-87-opacity, + 900: $black-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-amber: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: $black-87-opacity, + 900: $black-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $black-87-opacity, + ), + $md-orange: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $black-87-opacity, + 700: $black-87-opacity, + 800: white, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: black, + ), + $md-deep-orange: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: white, + 600: white, + 700: white, + 800: white, + 900: white, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: white, + A700: white, + ), + $md-brown: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: white, + 400: white, + 500: $white-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: white, + A700: $white-87-opacity, + ), + $md-grey: ( + 0: $black-87-opacity, + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: $black-87-opacity, + 500: $black-87-opacity, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + 1000: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: $black-87-opacity, + A700: $white-87-opacity, + ), + $md-blue-grey: ( + 50: $black-87-opacity, + 100: $black-87-opacity, + 200: $black-87-opacity, + 300: $black-87-opacity, + 400: white, + 500: white, + 600: $white-87-opacity, + 700: $white-87-opacity, + 800: $white-87-opacity, + 900: $white-87-opacity, + A100: $black-87-opacity, + A200: $black-87-opacity, + A400: white, + A700: $white-87-opacity, + ), +); diff --git a/modules/angular2_material/src/core/style/shadows.scss b/modules/angular2_material/src/core/style/shadows.scss new file mode 100644 index 0000000000..572629e2b2 --- /dev/null +++ b/modules/angular2_material/src/core/style/shadows.scss @@ -0,0 +1,9 @@ +// Elements can have an "elevation" from 1 to 5, signified by shadows. +// See http://google.com/design/spec/what-is-material/objects-in-3d-space.html +.md-shadow-bottom-z-1 { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); +} + +.md-shadow-bottom-z-2 { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); +} diff --git a/modules/angular2_material/src/core/style/theme-functions.scss b/modules/angular2_material/src/core/style/theme-functions.scss new file mode 100644 index 0000000000..f4611bd7ae --- /dev/null +++ b/modules/angular2_material/src/core/style/theme-functions.scss @@ -0,0 +1,56 @@ +// ** Two main functions for users ** +// md-palette: used for defining your theme in terms of Material hues. +// md-color: apply colors to components from the palette. Consumes the output of md-palette + + +// For a given hue in a palette, return the contrast color from the map of contrast palettes. +@function md-contrast($color-map, $hue, $contrast-color-map) { + @return map-get(map-get($contrast-color-map, $color-map), $hue); +} + + +// Creates a map of hues to colors for a theme. +// $color-map +// $primary +// $lighter +// $darker +@function md-palette($color-map, $primary, $lighter, $darker, $contrast-color-map) { + $result: map_merge($color-map, ( + default: map-get($color-map, $primary), + lighter: map-get($color-map, $lighter), + darker: map-get($color-map, $darker), + + default-contrast: md-contrast($color-map, $primary, $contrast-color-map), + lighter-contrast: md-contrast($color-map, $lighter, $contrast-color-map), + darker-contrast: md-contrast($color-map, $darker, $contrast-color-map) + )); + + // For each hue in the palette, add a "-contrast" color to the map. + @each $hue, $color in $color-map { + $result: map_merge($result, ( + "#{$hue}-contrast": md-contrast($color-map, $hue, $contrast-color-map) + )) + } + + @return $result; +} + +// Gets a color for a material design component. +// $color-map: a map of {key: color}. +// $hue-key: key used to lookup the color in $colorMap. Defaults to 'default' +// If $hue-key is a number between 0 and 1, it will be treated as $opacity. +// $opacity: the opacity to apply to the color. +@function md-color($color-map, $hue-key: default, $opacity: 1) { + // If hueKey is a number between zero and one, then it actually contains an + // opacity value, so recall this function with the default hue and that given opacity. + @if type-of($hue-key) == number and $hue-key >= 0 and $hue-key <= 1 { + @return md-color($color-map, default, $hue-key) + } + + $color: map-get($color-map, $hue-key); + $opacity: if(opacity($color) < 1, opacity($color), $opacity); + + @return rgba($color, $opacity); +} + + diff --git a/modules/angular2_material/src/core/style/variables.scss b/modules/angular2_material/src/core/style/variables.scss new file mode 100644 index 0000000000..d6a13addc1 --- /dev/null +++ b/modules/angular2_material/src/core/style/variables.scss @@ -0,0 +1,56 @@ +// Font +$font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif !default; + + +// Layout +$baseline-grid: 8px !default; +$layout-breakpoint-sm: 600px !default; +$layout-breakpoint-md: 960px !default; +$layout-breakpoint-lg: 1200px !default; +$layout-gutter-width: ($baseline-grid * 2) !default; + + +// App bar variables +$app-bar-height: 64px; + + +// Toast +$toast-height: $baseline-grid * 3 !default; +$toast-margin: $baseline-grid * 1 !default; + + +// Whiteframes +$whiteframe-shadow-z1: 0px 2px 5px 0 rgba(0,0,0,0.26) !default; +$whiteframe-zindex-z1: 1 !default; +$whiteframe-shadow-z2: 0px 8px 17px rgba(0,0,0,0.2) !default; +$whiteframe-zindex-z2: 2 !default; +$whiteframe-shadow-z3: 0px 17px 50px rgba(0,0,0,0.19) !default; +$whiteframe-zindex-z3: 3 !default; +$whiteframe-shadow-z4: 0px 16px 28px 0 rgba(0,0,0,0.22) !default; +$whiteframe-zindex-z4: 4 !default; +$whiteframe-shadow-z5: 0px 27px 24px 0 rgba(0,0,0,0.2) !default; +$whiteframe-zindex-z5: 5 !default; + + +// Z-indexes +$z-index-tooltip: 100 !default; +$z-index-dialog: 80 !default; +$z-index-toast: 90 !default; +$z-index-bottom-sheet: 70 !default; +$z-index-sidenav: 60 !default; +$z-index-backdrop: 50 !default; +$z-index-fab: 20 !default; + + +// Easing Curves +$swift-ease-out-duration: 0.4s !default; +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +$swift-ease-in-duration: 0.3s !default; +$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; + +$swift-ease-in-out-duration: 0.5s !default; +$swift-ease-in-out-timing-function: cubic-bezier(0.35, 0, 0.25, 1) !default; +$swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default; diff --git a/modules/angular2_material/src/core/theme.js b/modules/angular2_material/src/core/theme.js new file mode 100644 index 0000000000..e0ccf64cb0 --- /dev/null +++ b/modules/angular2_material/src/core/theme.js @@ -0,0 +1,12 @@ +import {Decorator} from 'angular2/angular2'; + +@Decorator({ + selector: '[md-theme]' +}) +export class MdTheme { + color: string; + + constructor() { + this.color = 'sky-blue' + } +} diff --git a/modules/examples/pubspec.yaml b/modules/examples/pubspec.yaml index 519b5d7fad..b95fd853ca 100644 --- a/modules/examples/pubspec.yaml +++ b/modules/examples/pubspec.yaml @@ -3,10 +3,13 @@ environment: sdk: '>=1.4.0' dependencies: angular2: '^<%= packageJson.version %>' + angular2_material: '^<%= packageJson.version %>' browser: '>=0.10.0 <0.11.0' dependency_overrides: angular2: path: ../angular2 + angular2_material: + path: ../angular2_material dev_dependencies: guinness: ">=0.1.17 <0.2.0" benchpress: @@ -16,7 +19,7 @@ transformers: entry_points: web/src/hello_world/index_common.dart reflection_entry_points: web/src/hello_world/index.dart - $dart2js: - minify: true - commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info] + minify: false + #commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info] #commandLineOptions: [--trust-type-annotations, --dump-info] - #commandLineOptions: [--dump-info] + commandLineOptions: [--dump-info] diff --git a/modules/examples/src/material/button/demo_app.html b/modules/examples/src/material/button/demo_app.html new file mode 100644 index 0000000000..52dbe76812 --- /dev/null +++ b/modules/examples/src/material/button/demo_app.html @@ -0,0 +1,81 @@ + + +

Button demo

+ +

+ You just clicked: {{previousClick}} +

+ +
+
+ + +
+ + form submit +
+ +
+ Regular button + + + + + + + +
+ +
+ Raised button + + + + + +
+
+ Fab button + + + + + +
+
+ Anchor / hyperlink + HREF + DISABLED HREF + RAISED HREF +
+ + +

+ Repeated button: + + {{clickCount}} +

diff --git a/modules/examples/src/material/button/index.html b/modules/examples/src/material/button/index.html new file mode 100644 index 0000000000..bda1a1bbe3 --- /dev/null +++ b/modules/examples/src/material/button/index.html @@ -0,0 +1,16 @@ + + + + + ng-material button demo + + + + + + Loading... + + $SCRIPTS$ + + + diff --git a/modules/examples/src/material/button/index.js b/modules/examples/src/material/button/index.js new file mode 100644 index 0000000000..7237e06f6c --- /dev/null +++ b/modules/examples/src/material/button/index.js @@ -0,0 +1,46 @@ +import {bootstrap, Component, View, MapWrapper, ListWrapper, For} from 'angular2/angular2'; +import {MdButton, MdAnchor} from 'angular2_material/src/components/button/button' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app' +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdButton, MdAnchor, For] +}) +class DemoApp { + previousClick: string; + action: string; + clickCount: number; + items: List; + + constructor() { + this.previousClick = 'Nothing'; + this.action = "ACTIVATE"; + this.clickCount = 0; + this.items = [1,2,3,4,5,6,7,8,9,0]; + } + + click(msg: string) { + this.previousClick = msg; + } + + submit(msg: string, event) { + event.preventDefault(); + this.previousClick = msg; + } + + increment() { + this.clickCount++; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/modules/examples/src/material/checkbox/demo_app.html b/modules/examples/src/material/checkbox/demo_app.html new file mode 100644 index 0000000000..cac15cdbf9 --- /dev/null +++ b/modules/examples/src/material/checkbox/demo_app.html @@ -0,0 +1,9 @@ +
+

Checkbox demo

+ + Normal checkbox + Primary checkbox + Disabled checkbox + +

Toggle count: {{toggleCount}}

+
diff --git a/modules/examples/src/material/checkbox/index.html b/modules/examples/src/material/checkbox/index.html new file mode 100644 index 0000000000..5f811095b1 --- /dev/null +++ b/modules/examples/src/material/checkbox/index.html @@ -0,0 +1,18 @@ + + + + + ng-material checkbox demo + + + + + +$SCRIPTS$ + Loading... + + diff --git a/modules/examples/src/material/checkbox/index.js b/modules/examples/src/material/checkbox/index.js new file mode 100644 index 0000000000..d60c20d2a5 --- /dev/null +++ b/modules/examples/src/material/checkbox/index.js @@ -0,0 +1,31 @@ +import {bootstrap, Component, View} from 'angular2/angular2'; +import {MdCheckbox} from 'angular2_material/src/components/checkbox/checkbox' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app' +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdCheckbox] +}) +class DemoApp { + toggleCount: number; + + constructor() { + this.toggleCount = 0; + } + + increment() { + this.toggleCount++; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/modules/examples/src/material/demo_common.js b/modules/examples/src/material/demo_common.js new file mode 100644 index 0000000000..0223109593 --- /dev/null +++ b/modules/examples/src/material/demo_common.js @@ -0,0 +1,71 @@ +import {IMPLEMENTS, print} from 'angular2/src/facade/lang'; +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {isPresent, isBlank, RegExpWrapper, StringWrapper, BaseException} from 'angular2/src/facade/lang'; +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {Injectable} from 'angular2/di'; +import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities'; +import {reflector} from 'angular2/src/reflection/reflection'; +import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; + +export function commonDemoSetup(): void { + BrowserDomAdapter.makeCurrent(); + reflector.reflectionCapabilities = new ReflectionCapabilities(); +} + +@Injectable() +@IMPLEMENTS(UrlResolver) +export class DemoUrlResolver { + static a; + + isInPubServe:boolean; + + constructor() { + if (isBlank(UrlResolver.a)) { + UrlResolver.a = DOM.createElement('a'); + } + this.isInPubServe = _isInPubServe(); + } + + resolve(baseUrl: string, url: string): string { + if (isBlank(baseUrl)) { + DOM.resolveAndSetHref(UrlResolver.a, url, null); + return DOM.getHref(UrlResolver.a); + } + + if (isBlank(url) || url == '') return baseUrl; + + if (url[0] == '/') { + throw new BaseException(`Could not resolve the url ${url} from ${baseUrl}`); + } + + var m = RegExpWrapper.firstMatch(_schemeRe, url); + + if (isPresent(m[1])) { + return url; + } + + if (StringWrapper.startsWith(url, './')) { + return `${baseUrl}/${url}`; + } + + if (this.isInPubServe) { + return `/packages/${url}`; + } else { + return `/${url}`; + } + } +} + +var _schemeRe = RegExpWrapper.create('^([^:/?#]+:)?'); + +// TODO: remove this hack when http://dartbug.com/23128 is fixed +function _isInPubServe():boolean { + try { + int.parse('123'); + print('>> Running in Dart'); + return true; + } catch(_) { + print('>> Running in JS'); + return false; + } +} diff --git a/modules/examples/src/material/grid_list/demo_app.html b/modules/examples/src/material/grid_list/demo_app.html new file mode 100644 index 0000000000..7ce072e541 --- /dev/null +++ b/modules/examples/src/material/grid_list/demo_app.html @@ -0,0 +1,29 @@ + + + + +
+ +

grid-list demo

+ + + + + Tile #1 + + + + Tile #2 + + + + Tile #3 + + + + +
diff --git a/modules/examples/src/material/grid_list/index.html b/modules/examples/src/material/grid_list/index.html new file mode 100644 index 0000000000..4beb8fe846 --- /dev/null +++ b/modules/examples/src/material/grid_list/index.html @@ -0,0 +1,20 @@ + + + + + ng-material grid-list demo + + + + + +$SCRIPTS$ + + Loading... + + + diff --git a/modules/examples/src/material/grid_list/index.js b/modules/examples/src/material/grid_list/index.js new file mode 100644 index 0000000000..2d938c90ad --- /dev/null +++ b/modules/examples/src/material/grid_list/index.js @@ -0,0 +1,30 @@ +import {bootstrap, Component, View} from 'angular2/angular2'; +import {MdGridList, MdGridTile} from 'angular2_material/src/components/grid_list/grid_list' +import {MdTheme} from 'angular2_material/src/core/theme' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app' +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdGridList, MdGridTile] +}) +class DemoApp { + tile3RowSpan: number; + tile3ColSpan: number; + + constructor() { + this.tile3RowSpan = 3; + this.tile3ColSpan = 3; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/modules/examples/src/material/input/demo_app.html b/modules/examples/src/material/input/demo_app.html new file mode 100644 index 0000000000..dcb97f4e92 --- /dev/null +++ b/modules/examples/src/material/input/demo_app.html @@ -0,0 +1,10 @@ + +
+

input demo

+ + + + + + +
diff --git a/modules/examples/src/material/input/index.html b/modules/examples/src/material/input/index.html new file mode 100644 index 0000000000..8ddc4729bf --- /dev/null +++ b/modules/examples/src/material/input/index.html @@ -0,0 +1,19 @@ + + + + + ng-material input demo + + + + + + $SCRIPTS$ + + Loading... + + diff --git a/modules/examples/src/material/input/index.js b/modules/examples/src/material/input/index.js new file mode 100644 index 0000000000..d60c20d2a5 --- /dev/null +++ b/modules/examples/src/material/input/index.js @@ -0,0 +1,31 @@ +import {bootstrap, Component, View} from 'angular2/angular2'; +import {MdCheckbox} from 'angular2_material/src/components/checkbox/checkbox' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app' +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdCheckbox] +}) +class DemoApp { + toggleCount: number; + + constructor() { + this.toggleCount = 0; + } + + increment() { + this.toggleCount++; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/modules/examples/src/material/progress-linear/demo_app.html b/modules/examples/src/material/progress-linear/demo_app.html new file mode 100644 index 0000000000..5f99e6ff0a --- /dev/null +++ b/modules/examples/src/material/progress-linear/demo_app.html @@ -0,0 +1,42 @@ + + +
+

Progress-linear demo

+ +

+ Determinate: primary + + +

+ +

+ Determinate: accent + + +

+ +

+ Buffer + + +

+ +

+ Indeterminate + + +

+ +

+ Query + + +

+ + + +

Progress: {{progress}}

+ + +
diff --git a/modules/examples/src/material/progress-linear/index.html b/modules/examples/src/material/progress-linear/index.html new file mode 100644 index 0000000000..831ce43345 --- /dev/null +++ b/modules/examples/src/material/progress-linear/index.html @@ -0,0 +1,18 @@ + + + + + ng-material progress-linear demo + + + + + +$SCRIPTS$ + Loading... + + diff --git a/modules/examples/src/material/progress-linear/index.js b/modules/examples/src/material/progress-linear/index.js new file mode 100644 index 0000000000..fc67ab81a1 --- /dev/null +++ b/modules/examples/src/material/progress-linear/index.js @@ -0,0 +1,31 @@ +import {bootstrap, Component, View} from 'angular2/angular2'; +import {MdProgressLinear} from 'angular2_material/src/components/progress-linear/progress_linear' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app' +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdProgressLinear] +}) +class DemoApp { + progress: number; + + constructor() { + this.progress = 40; + } + + step(s: number) { + this.progress += s; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/modules/examples/src/material/radio/demo_app.html b/modules/examples/src/material/radio/demo_app.html new file mode 100644 index 0000000000..d143376606 --- /dev/null +++ b/modules/examples/src/material/radio/demo_app.html @@ -0,0 +1,35 @@ +
+

Radio buttons

+

Inside of a radiogroup

+ + + Star Wars + Star Trek + Battlestar Galactica + Dr. Who + + +

Your selection: {{scifi.value}}

+

radio group value change count: {{groupValueChangeCount}}

+ +
+

Standalone

+ + Earth + Fire + Wind + Heart (disabled) + +

individual radio value change count: {{individualValueChanges}}

+ +
+

Disabled radio group

+

Chosen: {{pokemon}}

+ + Charmander + Bulbasaur + Squirtle + + + +
diff --git a/modules/examples/src/material/radio/index.html b/modules/examples/src/material/radio/index.html new file mode 100644 index 0000000000..d39033ce6d --- /dev/null +++ b/modules/examples/src/material/radio/index.html @@ -0,0 +1,20 @@ + + + + + ng-material radio demo + + + + + +$SCRIPTS$ + + Loading... + + + diff --git a/modules/examples/src/material/radio/index.js b/modules/examples/src/material/radio/index.js new file mode 100644 index 0000000000..6724c27ce5 --- /dev/null +++ b/modules/examples/src/material/radio/index.js @@ -0,0 +1,49 @@ +import {bootstrap, Component, View} from 'angular2/angular2'; +import {MdRadioButton, MdRadioGroup} from 'angular2_material/src/components/radio/radio_button' +import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app', + injectables: [MdRadioDispatcher] +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdRadioGroup, MdRadioButton] +}) +class DemoApp { + thirdValue; + groupValueChangeCount; + individualValueChanges; + pokemon; + someTabindex; + + constructor() { + this.thirdValue = 'dr-who'; + this.groupValueChangeCount = 0; + this.individualValueChanges = 0; + this.pokemon = ''; + this.someTabindex = 888; + } + + chooseCharmander() { + this.pokemon = 'fire'; + } + + onGroupChange() { + this.groupValueChangeCount++; + } + + onIndividualClick() { + this.individualValueChanges++; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/modules/examples/src/material/switcher/demo_app.html b/modules/examples/src/material/switcher/demo_app.html new file mode 100644 index 0000000000..50dc84ed57 --- /dev/null +++ b/modules/examples/src/material/switcher/demo_app.html @@ -0,0 +1,9 @@ +
+

Switch demo

+ + Normal switch + Primary switch + Disabled switch + +

Toggle count: {{toggleCount}}

+
diff --git a/modules/examples/src/material/switcher/index.html b/modules/examples/src/material/switcher/index.html new file mode 100644 index 0000000000..b521ba8659 --- /dev/null +++ b/modules/examples/src/material/switcher/index.html @@ -0,0 +1,19 @@ + + + + + ng-material switch demo + + + + + + $SCRIPTS$ + + Loading... + + diff --git a/modules/examples/src/material/switcher/index.js b/modules/examples/src/material/switcher/index.js new file mode 100644 index 0000000000..bdd61b41d7 --- /dev/null +++ b/modules/examples/src/material/switcher/index.js @@ -0,0 +1,31 @@ +import {bootstrap, Component, View} from 'angular2/angular2'; +import {MdSwitch} from 'angular2_material/src/components/switcher/switch' +import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {commonDemoSetup, DemoUrlResolver} from '../demo_common'; +import {bind} from 'angular2/di'; + +@Component({ + selector: 'demo-app' +}) +@View({ + templateUrl: './demo_app.html', + directives: [MdSwitch] +}) +class DemoApp { + toggleCount: number; + + constructor() { + this.toggleCount = 0; + } + + increment() { + this.toggleCount++; + } +} + +export function main() { + commonDemoSetup(); + bootstrap(DemoApp, [ + bind(UrlResolver).toValue(new DemoUrlResolver()) + ]); +} diff --git a/package.json b/package.json index ccaf4fb117..bbb2f54d85 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "fs-extra": "^0.16.4", "glob": "^4.0.6", "gulp": "^3.8.8", + "gulp-autoprefixer": "^2.1.0", "gulp-changed": "^1.0.0", "gulp-clang-format": "^1.0.3", "gulp-concat": "^2.5.2", @@ -68,6 +69,7 @@ "gulp-jasmine": "^1.0.1", "gulp-load-plugins": "^0.7.1", "gulp-rename": "^1.2.0", + "gulp-sass": "^1.3.3", "gulp-shell": "^0.2.10", "gulp-sourcemaps": "1.3.*", "gulp-template": "^3.0.0", diff --git a/tools/broccoli/trees/dart_tree.ts b/tools/broccoli/trees/dart_tree.ts index 8a3077fb79..48f4386781 100644 --- a/tools/broccoli/trees/dart_tree.ts +++ b/tools/broccoli/trees/dart_tree.ts @@ -132,7 +132,7 @@ function getDocsTree() { var mdTree = stew.rename(modulesFunnel(['**/*.dart.md']), relativePath => relativePath.replace(/\.dart\.md$/, '.md')); // Copy all assets, ignore .js. and .dart. (handled above). - var docs = modulesFunnel(['**/*.md', '**/*.png', '**/*.html', '**/*.css'], + var docs = modulesFunnel(['**/*.md', '**/*.png', '**/*.html', '**/*.css', '**/*.scss'], ['**/*.js.md', '**/*.dart.md']); return mergeTrees([licenses, mdTree, docs]); }