diff --git a/ionic/components/button/button-size.scss b/ionic/components/button/button-size.scss deleted file mode 100644 index db45559bb3..0000000000 --- a/ionic/components/button/button-size.scss +++ /dev/null @@ -1,25 +0,0 @@ -@import "../../globals.core"; - -// Button Sizes -// -------------------------------------------------- - -$button-large-font-size: 2rem !default; -$button-large-height: 2.8em !default; -$button-large-padding: 1.0em !default; - -$button-small-font-size: 1.3rem !default; -$button-small-height: 2.1em !default; -$button-small-padding: 0.9em !default; - - -.button-large { - padding: 0 $button-large-padding; - min-height: $button-large-height; - font-size: $button-large-font-size; -} - -.button-small { - padding: 0 $button-small-padding; - min-height: $button-small-height; - font-size: $button-small-font-size; -} diff --git a/ionic/components/button/button.ts b/ionic/components/button/button.ts index 2427a568b7..afea672cc0 100644 --- a/ionic/components/button/button.ts +++ b/ionic/components/button/button.ts @@ -1,16 +1,12 @@ -import {Directive, ElementRef, Renderer, Attribute} from 'angular2/angular2'; +import {Directive, ElementRef, Renderer, Attribute, Optional} from 'angular2/angular2'; import {Config} from '../../config/config'; +import {Toolbar} from '../toolbar/toolbar'; /** * @name Button * @module ionic - * @property [primary] - sets button color to default primary - * @property [secondary] - sets button color to default secondary - * @property [danger] - sets button color to default danger - * @property [light] - sets button color to default light - * @property [dark] - sets button color to default dark * @property [outline] - for an unfilled outline button * @property [clear] - for a transparent button that only shows text and icons * @property [round] - for a button with rounded corners @@ -36,10 +32,14 @@ export class Button { constructor( config: Config, - elementRef: ElementRef, - renderer: Renderer, - @Attribute('type') type: string + private elementRef: ElementRef, + private renderer: Renderer ) { + this._role = 'button'; // bar-button + this._size = null; // large + this._style = null; // outline + this._display = null; // block + this._colors = []; // primary let element = elementRef.nativeElement; @@ -49,16 +49,11 @@ export class Button { if (element.hasAttribute('ion-item')) { // no need to put on these classes for an ion-item + this._role = null; return; } - renderer.setElementClass(elementRef, 'button', true); - - this.attrToClass(renderer, elementRef); - - if (type) { - renderer.setElementAttribute(elementRef, type, ''); - } + this._readAttrs(element); // figure out if and where the icon lives in the button let childNodes = element.childNodes; @@ -98,47 +93,65 @@ export class Button { } - attrToClass(renderer, elementRef) { - // looping through native dom attributes, eww - // https://github.com/angular/angular/issues/1818 + afterContentInit() { + this._assignCss(true); + } - let element = elementRef.nativeElement; - let attrs = element.attributes; - let btnAttrs = []; - - BUTTON_SIZE_ATTRS.forEach(buttonAttr => { - if (element.hasAttribute(buttonAttr)) { - renderer.setElementClass(elementRef, 'button-' + buttonAttr, true); - } - }); - - BUTTON_TYPE_ATTRS.forEach(buttonAttr => { - if (element.hasAttribute(buttonAttr)) { - renderer.setElementClass(elementRef, 'button-' + buttonAttr, true); - btnAttrs.push(buttonAttr); - } - }); + setRole(val) { + this._role = val; + } + _readAttrs(element) { + let elementAttrs = element.attributes; let attrName; - for (let i = 0, l = attrs.length; i < l; i++) { - attrName = attrs[i].name; - if (attrs[i].value === '' && COMMON_HTML_ATTR.test(attrName) !== true && BUTTON_TYPE_ATTRS.indexOf(attrName) < 0 && BUTTON_SIZE_ATTRS.indexOf(attrName) < 0) { - if (btnAttrs.length) { - btnAttrs.forEach(buttonAttr => { - renderer.setElementClass(elementRef, 'button-' + buttonAttr + '-' + attrName, true); - }); - } else { - renderer.setElementClass(elementRef, 'button-' + attrName, true); - } + for (let i = 0, l = elementAttrs.length; i < l; i++) { + if (elementAttrs[i].value !== '') continue; + + attrName = elementAttrs[i].name; + + if (BUTTON_STYLE_ATTRS.indexOf(attrName) > -1) { + this._style = attrName; + + } else if (BUTTON_DISPLAY_ATTRS.indexOf(attrName) > -1) { + this._display = attrName; + + } else if (BUTTON_SIZE_ATTRS.indexOf(attrName) > -1) { + this._size = attrName; + + } else if (!(IGNORE_ATTRS.test(attrName))) { + this._colors.push(attrName); } } } + _assignCss(assignCssClass) { + let setElementClass = this.renderer.setElementClass; + let elementRef = this.elementRef; + let role = this._role; + if (role) { + setElementClass(elementRef, role, assignCssClass); // button + if (this._style) setElementClass(elementRef, role + '-' + this._style, assignCssClass); // button-clear + if (this._display) setElementClass(elementRef, role + '-' + this._display, assignCssClass); // button-full + if (this._size) setElementClass(elementRef, role + '-' + this._size, assignCssClass); // button-small + this._colors.forEach(color => { + setElementClass(elementRef, role + '-' + color, assignCssClass); // button-secondary + }); + } + } + + static setRoles(contentButtonChildren, role) { + let buttons = contentButtonChildren.toArray(); + buttons.forEach(button => { + button.setRole(role); + }); + } + } -const COMMON_HTML_ATTR = /id|class|type|href|ng-/; -const BUTTON_TYPE_ATTRS = ['block', 'full', 'round', 'clear', 'outline']; const BUTTON_SIZE_ATTRS = ['large', 'small']; +const BUTTON_STYLE_ATTRS = ['round', 'clear', 'outline', 'fab']; +const BUTTON_DISPLAY_ATTRS = ['block', 'full']; +const IGNORE_ATTRS = /_ng/; const TEXT = 1; const ICON = 2; diff --git a/ionic/components/button/modes/ios.scss b/ionic/components/button/modes/ios.scss index d961c43bda..c49dd925f8 100644 --- a/ionic/components/button/modes/ios.scss +++ b/ionic/components/button/modes/ios.scss @@ -14,6 +14,13 @@ $button-ios-text-color: inverse($button-ios-color) !default; $button-ios-hover-opacity: 0.8 !default; $button-ios-border-radius: 4px !default; +$button-ios-large-font-size: 2rem !default; +$button-ios-large-height: 2.8em !default; +$button-ios-large-padding: 1.0em !default; +$button-ios-small-font-size: 1.3rem !default; +$button-ios-small-height: 2.1em !default; +$button-ios-small-padding: 0.9em !default; + // iOS Default Button // -------------------------------------------------- @@ -62,6 +69,22 @@ $button-ios-border-radius: 4px !default; } +// iOS Button Sizes +// -------------------------------------------------- + +.button-large { + padding: 0 $button-ios-large-padding; + min-height: $button-ios-large-height; + font-size: $button-ios-large-font-size; +} + +.button-small { + padding: 0 $button-ios-small-padding; + min-height: $button-ios-small-height; + font-size: $button-ios-small-font-size; +} + + // iOS Block Button // -------------------------------------------------- @@ -160,4 +183,3 @@ $button-ios-border-radius: 4px !default; @import "../button-fab"; @import "../button-icon"; -@import "../button-size"; diff --git a/ionic/components/button/modes/md.scss b/ionic/components/button/modes/md.scss index a3611e5c27..154e960525 100644 --- a/ionic/components/button/modes/md.scss +++ b/ionic/components/button/modes/md.scss @@ -26,6 +26,13 @@ $button-md-color-activated: color-shade($button-md-color) !defaul $button-md-text-color: inverse($button-md-color) !default; $button-md-hover-opacity: 0.8 !default; +$button-md-large-font-size: 2rem !default; +$button-md-large-height: 2.8em !default; +$button-md-large-padding: 1.0em !default; +$button-md-small-font-size: 1.3rem !default; +$button-md-small-height: 2.1em !default; +$button-md-small-padding: 0.9em !default; + // Material Design Default Button // -------------------------------------------------- @@ -87,15 +94,15 @@ $button-md-hover-opacity: 0.8 !default; // -------------------------------------------------- .button-large { - padding: 0 $button-large-padding; - min-height: $button-large-height; - font-size: $button-large-font-size; + padding: 0 $button-md-large-padding; + min-height: $button-md-large-height; + font-size: $button-md-large-font-size; } .button-small { - padding: 0 $button-small-padding; - min-height: $button-small-height; - font-size: $button-small-font-size; + padding: 0 $button-md-small-padding; + min-height: $button-md-small-height; + font-size: $button-md-small-font-size; } @@ -254,4 +261,3 @@ $button-md-hover-opacity: 0.8 !default; @import "../button-fab"; @import "../button-icon"; -@import "../button-size"; diff --git a/ionic/components/button/test/button.spec.ts b/ionic/components/button/test/button.spec.ts new file mode 100644 index 0000000000..e37a0c9f9a --- /dev/null +++ b/ionic/components/button/test/button.spec.ts @@ -0,0 +1,160 @@ +import {Button, Config} from 'ionic/ionic'; + +export function run() { + + function mockButton(attrs, config) { + config = config || new Config(); + let elementRef = { + nativeElement: document.createElement('button') + }; + if (attrs) { + for (let i = 0; i < attrs.length; i++) { + elementRef.nativeElement.setAttribute(attrs[i], ''); + } + } + let renderer = { + setElementClass: function(elementRef, className, shouldAdd) { + elementRef.nativeElement.classList[shouldAdd ? 'add' : 'remove'](className); + } + }; + return new Button(config, elementRef, renderer); + } + + function hasClass(button, className) { + return button.elementRef.nativeElement.classList.contains(className); + } + + it('should set a different button role', () => { + let b = mockButton(['outline', 'small', 'full', 'primary']); + b.setRole('bar-button') + b._assignCss(true); + expect(hasClass(b, 'bar-button-outline')).toEqual(true); + expect(hasClass(b, 'bar-button-small')).toEqual(true); + expect(hasClass(b, 'bar-button-full')).toEqual(true); + expect(hasClass(b, 'bar-button-primary')).toEqual(true); + + expect(hasClass(b, 'button-outline')).toEqual(false); + expect(hasClass(b, 'button-small')).toEqual(false); + expect(hasClass(b, 'button-full')).toEqual(false); + expect(hasClass(b, 'button-primary')).toEqual(false); + }); + + it('should remove button color attributes and add different role', () => { + let b = mockButton(['outline', 'small', 'full', 'primary']); + b._assignCss(true); + expect(hasClass(b, 'button-outline')).toEqual(true); + expect(hasClass(b, 'button-small')).toEqual(true); + expect(hasClass(b, 'button-full')).toEqual(true); + expect(hasClass(b, 'button-primary')).toEqual(true); + + b._assignCss(false); + expect(hasClass(b, 'button-outline')).toEqual(false); + expect(hasClass(b, 'button-small')).toEqual(false); + expect(hasClass(b, 'button-full')).toEqual(false); + expect(hasClass(b, 'button-primary')).toEqual(false); + }); + + it('should read button color attributes', () => { + let b = mockButton(['primary']); + b._assignCss(true); + expect(hasClass(b, 'button-primary')).toEqual(true); + + b = mockButton(['primary', 'secondary']); + b._assignCss(true); + expect(hasClass(b, 'button-primary')).toEqual(true); + expect(hasClass(b, 'button-secondary')).toEqual(true); + + b = mockButton(['outline', 'small', 'full', 'primary']); + b._assignCss(true); + expect(hasClass(b, 'button-outline')).toEqual(true); + expect(hasClass(b, 'button-small')).toEqual(true); + expect(hasClass(b, 'button-full')).toEqual(true); + expect(hasClass(b, 'button-primary')).toEqual(true); + }); + + it('should read button style attributes', () => { + let b = mockButton(['round']); + b._assignCss(true); + expect(hasClass(b, 'button-round')).toEqual(true); + + b = mockButton(['clear']); + b._assignCss(true); + expect(hasClass(b, 'button-clear')).toEqual(true); + + b = mockButton(['outline']); + b._assignCss(true); + expect(hasClass(b, 'button-outline')).toEqual(true); + + b = mockButton(['fab']); + b._assignCss(true); + expect(hasClass(b, 'button-fab')).toEqual(true); + + b = mockButton(['clear', 'outline', 'small', 'full']); + b._assignCss(true); + expect(hasClass(b, 'button-clear')).toEqual(false); + expect(hasClass(b, 'button-outline')).toEqual(true); + expect(hasClass(b, 'button-small')).toEqual(true); + expect(hasClass(b, 'button-full')).toEqual(true); + }); + + it('should read button display attributes', () => { + let b = mockButton(['block']); + b._assignCss(true); + expect(hasClass(b, 'button-block')).toEqual(true); + + b = mockButton(['full']); + b._assignCss(true); + expect(hasClass(b, 'button-full')).toEqual(true); + + b = mockButton(['block', 'full']); + b._assignCss(true); + expect(hasClass(b, 'button-block')).toEqual(false); + expect(hasClass(b, 'button-full')).toEqual(true); + }); + + it('should read button size attributes', () => { + let b = mockButton(['small']); + b._assignCss(true); + expect(hasClass(b, 'button-small')).toEqual(true); + + b = mockButton(['large']); + b._assignCss(true); + expect(hasClass(b, 'button-large')).toEqual(true); + + b = mockButton(['large', 'small']); + b._assignCss(true); + expect(hasClass(b, 'button-large')).toEqual(false); + expect(hasClass(b, 'button-small')).toEqual(true); + }); + + it('should not add button css class for ion-item attribute', () => { + let b = mockButton(['ion-item']); + b._assignCss(true); + expect(hasClass(b, 'button')).toEqual(false); + }); + + it('should add button css class', () => { + let b = mockButton(); + b._assignCss(true); + expect(hasClass(b, 'button')).toEqual(true); + }); + + it('should add disable-hover css class', () => { + let config = new Config({ + hoverCSS: false + }); + let b = mockButton(null, config); + + expect(hasClass(b, 'disable-hover')).toEqual(true); + }); + + it('should set defaults', () => { + let b = mockButton(); + expect(b._role).toEqual('button'); + expect(b._size).toEqual(null); + expect(b._colors.length).toEqual(0); + expect(b._style).toEqual(null); + expect(b._display).toEqual(null); + }); + +} diff --git a/ionic/components/toolbar/toolbar.ts b/ionic/components/toolbar/toolbar.ts index 072780f3b8..c779731271 100644 --- a/ionic/components/toolbar/toolbar.ts +++ b/ionic/components/toolbar/toolbar.ts @@ -1,9 +1,10 @@ -import {Component, Directive, Host, ElementRef, Optional, forwardRef, Inject} from 'angular2/angular2'; +import {Component, Directive, Host, ElementRef, Optional, forwardRef, Inject, ContentChildren, ContentChild, QueryList} from 'angular2/angular2'; import {Ion} from '../ion'; import {Config} from '../../config/config'; import {MenuToggle} from '../menu/menu-toggle'; import {Navbar} from '../navbar/navbar'; +import {Button} from '../button/button'; /** @@ -63,8 +64,8 @@ export class ToolbarBase extends Ion { /** * @name Toolbar * @description - * The toolbar is generic bar that sits above content. - * Unlike an `ionNavbar`, `ionToolbar` can be used for a subheader as well. + * The toolbar is generic bar that sits above or below content. + * Unlike an `Navbar`, `Toolbar` can be used for a subheader as well. * @usage * ```html * @@ -89,6 +90,7 @@ export class ToolbarBase extends Ion { } }) export class Toolbar extends ToolbarBase { + constructor( elementRef: ElementRef, config: Config @@ -101,14 +103,14 @@ export class Toolbar extends ToolbarBase { /** * @name ToolbarTitle * @description - * `ion-title` is a component that sets the title of the `ionToolbar` or `ionNavbar` + * `ion-title` is a component that sets the title of the `Toolbar` or `Navbar` * @usage * ```html * * Tab 1 * * - * + * * * Tab 1 * @@ -159,4 +161,9 @@ export class ToolbarItem extends Ion { toolbar && toolbar.addItemRef(elementRef); navbar && navbar.addItemRef(elementRef); } + + @ContentChildren(Button) + set _buttons(buttons) { + Button.setRoles(buttons, 'bar-button'); + } } diff --git a/ionic/platform/platform.ts b/ionic/platform/platform.ts index 90d050833b..0a3b67e3a5 100644 --- a/ionic/platform/platform.ts +++ b/ionic/platform/platform.ts @@ -301,7 +301,10 @@ export class Platform { * @returns {boolean} TODO */ testNavigatorPlatform(navigatorPlatformExpression) { - return navigatorPlatformExpression.test(this._bPlt || ''); + if (navigatorPlatformExpression && this._bPlt) { + let rgx = new RegExp(navigatorPlatformExpression, 'i'); + return rgx.test(this._bPlt); + } } /** diff --git a/ionic/platform/registry.ts b/ionic/platform/registry.ts index 57233170df..abf3218527 100644 --- a/ionic/platform/registry.ts +++ b/ionic/platform/registry.ts @@ -52,7 +52,7 @@ Platform.register({ // however, under-powered devices shouldn't use ripple // if this a linux device, and is using Android Chrome v36 (Android 5.0) // or above then use ripple, otherwise do not use a ripple effect - if (p.testNavigatorPlatform('/linux/i')) { + if (p.testNavigatorPlatform(/linux/i)) { let chromeVersion = p.matchUserAgentVersion(/Chrome\/(\d+).(\d+)?/); if (chromeVersion) { // linux android device using modern android chrome browser gets ripple