Skip to content

Commit 6d280ea

Browse files
committed
feat(md-button): enhance button focus appearance.
1 parent 7354206 commit 6d280ea

File tree

3 files changed

+126
-35
lines changed

3 files changed

+126
-35
lines changed

modules/angular2_material/src/components/button/button.scss

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
// TODO(jelbourn): This goes away.
55
@import "../../core/style/default-theme";
66

7+
// TODO(jelbourn): Move variables and mixins into a partial file.
8+
// TODO(jelbourn): Measure perf benefits for translate3d and will-change.
9+
// TODO(jelbourn): Figure out if anchor hover underline actually happens in any browser.
10+
711

812
// Standard button sizing.
913
$md-button-padding: 0 rem(0.600) !default;
@@ -19,7 +23,7 @@ $md-fab-padding: rem(1.60) !default;
1923
$md-fab-mini-size: rem(4.00) !default;
2024
$md-fab-mini-line-height: rem(4.00) !default;
2125

22-
/** Mixin to create distinct classes for fab positions, e.g. ".md-fab-bottom-right". */
26+
/** Mixin to create distinct classes for fab positions, e.g. ".md-fab-position-bottom-right". */
2327
@mixin md-fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) {
2428
.md-fab-position-#{$spot} {
2529
top: $top;
@@ -30,14 +34,17 @@ $md-fab-mini-line-height: rem(4.00) !default;
3034
}
3135
}
3236

33-
// Base styles for all buttons.
37+
/** Styles for all disabled buttons. */
38+
@mixin md-button-disabled() {
39+
color: md-color($md-foreground, disabled);
40+
background-color: transparent;
41+
cursor: default;
42+
}
43+
44+
/** Base styles for all buttons. */
3445
@mixin md-button-base() {
3546
box-sizing: border-box;
3647
position: relative;
37-
display: inline-block;
38-
39-
font-size: $md-body-font-size-base;
40-
font-weight: 500;
4148

4249
// Reset browser <button> styles.
4350
background: transparent;
@@ -48,9 +55,15 @@ $md-fab-mini-line-height: rem(4.00) !default;
4855
outline: none;
4956
border: none;
5057

51-
// Apply nowrap and remove underline for anchor md-buttons.
58+
// Make anchors render like buttons.
59+
display: inline-block;
5260
white-space: nowrap;
5361
text-decoration: none;
62+
vertical-align: middle;
63+
64+
// Typography.
65+
font-size: $md-body-font-size-base;
66+
font-weight: 500;
5467

5568
// Sizing.
5669
padding: $md-button-padding;
@@ -60,18 +73,22 @@ $md-fab-mini-line-height: rem(4.00) !default;
6073
border-radius: $md-button-border-radius;
6174

6275
// Animation.
63-
// TODO(jelbourn): figure out where will-change would be beneficial.
64-
transition: background $swift-ease-out-duration $swift-ease-out-timing-function;
76+
transition: background $swift-ease-out-duration $swift-ease-out-timing-function,
77+
box-shadow $swift-ease-out-duration $swift-ease-out-timing-function;
6578

66-
// Hide the default browser focus indicator.
79+
// Hide the browser focus indicator, instead applying our own focus style on background-color.
6780
&:focus {
6881
outline: none;
6982
}
7083

7184
&:hover, &:focus {
7285
// Remove anchor underline again for more specific modifiers.
7386
text-decoration: none;
87+
}
7488

89+
// Use a CSS class for focus style because we only want to render the focus style when
90+
// the focus originated from a keyboard event (see JS source for more details).
91+
&:hover, &.md-button-focus {
7592
background: md-color($md-background, 500, 0.2);
7693
}
7794

@@ -83,10 +100,10 @@ $md-fab-mini-line-height: rem(4.00) !default;
83100
color: md-color($md-accent);
84101
}
85102

86-
&:disabled {
87-
color: md-color($md-foreground, disabled);
88-
background-color: transparent;
89-
cursor: default;
103+
// Use the [disabled] attribute instead of the :disabled pseudo-class because anchors
104+
// cannot technically be :disabled.
105+
&[disabled] {
106+
@include md-button-disabled();
90107
}
91108
}
92109

@@ -95,29 +112,40 @@ $md-fab-mini-line-height: rem(4.00) !default;
95112
@include md-button-base();
96113

97114
// Force hardware acceleration.
98-
// TODO(jelbourn): determine if this actually has an impact.
99115
transform: translate3d(0, 0, 0);
100-
box-shadow: $md-shadow-bottom-z-1;
101116

102-
transition: background $swift-ease-out-duration $swift-ease-out-timing-function,
103-
box-shadow $swift-ease-out-duration $swift-ease-out-timing-function;
117+
box-shadow: $md-shadow-bottom-z-1;
104118

105119
&:active {
106120
box-shadow: $md-shadow-bottom-z-2;
107121
}
108122

109-
&:disabled {
123+
&[disabled] {
110124
box-shadow: none;
111125
}
112126

113127
&.md-primary {
114128
color: md-color($md-primary, default-contrast);
115129
background-color: md-color($md-primary);
130+
131+
&:hover, &.md-button-focus {
132+
background-color: md-color($md-primary, 600);
133+
}
116134
}
117135

118136
&.md-accent {
119137
color: md-color($md-accent, default-contrast);
120138
background-color: md-color($md-accent);
139+
140+
&:hover, &.md-button-focus {
141+
background-color: md-color($md-accent, A700);
142+
}
143+
}
144+
145+
&.md-primary, &.md-accent {
146+
&[disabled] {
147+
@include md-button-disabled();
148+
}
121149
}
122150
}
123151

@@ -145,8 +173,6 @@ $md-fab-mini-line-height: rem(4.00) !default;
145173
line-height: $md-fab-line-height;
146174
vertical-align: middle;
147175

148-
// TODO(jelbourn): May need `background-clip: padding-box;` depending on ripple implementation.
149-
150176
&.md-mini {
151177
line-height: $md-fab-mini-line-height;
152178
width: $md-fab-mini-size;

modules/angular2_material/src/components/button/button.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,86 @@
11
import {Component, View, LifecycleEvent, ViewEncapsulation} from 'angular2/angular2';
2+
3+
import {TimerWrapper} from 'angular2/src/facade/async';
24
import {isPresent} from 'angular2/src/facade/lang';
35

6+
// TODO(jelbourn): Ink ripples.
7+
// TODO(jelbourn): Make the `isMosueDown` stuff done with one global listener.
48

5-
@Component({selector: '[md-button]:not(a), [md-fab]:not(a), [md-raised-button]:not(a)'})
9+
@Component({
10+
selector: '[md-button]:not(a), [md-fab]:not(a), [md-raised-button]:not(a)',
11+
host: {
12+
'(^mousedown)': 'onMousedown()',
13+
'(focus)': 'onFocus()',
14+
'(blur)': 'onBlur()',
15+
'[class.md-button-focus]': 'isKeyboardFocused',
16+
},
17+
})
618
@View({
719
templateUrl: 'package:angular2_material/src/components/button/button.html',
8-
encapsulation: ViewEncapsulation.NONE
20+
encapsulation: ViewEncapsulation.NONE,
921
})
1022
export class MdButton {
11-
// TODO(jelbourn): Ink ripples.
23+
/** Whether a mousedown has occured on this element in the last 100ms. */
24+
isMouseDown: boolean = false;
25+
26+
/** Whether the button has focus from the keyboard (not the mouse). Used for class binding. */
27+
isKeyboardFocused: boolean = false;
28+
29+
onMousedown() {
30+
// We only *show* the focus style when focus has come to the button via the keyboard.
31+
// The Material Design spec is silent on this topic, and without doing this, the
32+
// button continues to look :active after clicking.
33+
// @see http://marcysutton.com/button-focus-hell/
34+
this.isMouseDown = true;
35+
TimerWrapper.setTimeout(() => {this.isMouseDown = false}, 100);
36+
}
37+
38+
onFocus() {
39+
this.isKeyboardFocused = !this.isMouseDown;
40+
}
41+
42+
onBlur() {
43+
this.isKeyboardFocused = false;
44+
}
1245
}
1346

1447

1548
@Component({
16-
selector: '[md-button][href]',
49+
selector: 'a[md-button], a[md-raised-button], a[md-fab]',
1750
properties: ['disabled'],
18-
host: {'(click)': 'onClick($event)', '[tabIndex]': 'tabIndex'},
19-
lifecycle: [LifecycleEvent.onChange]
51+
lifecycle: [LifecycleEvent.onChange],
52+
host: {
53+
'(^click)': 'onClick($event)',
54+
'(^mousedown)': 'onMousedown()',
55+
'(focus)': 'onFocus()',
56+
'(blur)': 'onBlur()',
57+
'[tabIndex]': 'tabIndex',
58+
'[class.md-button-focus]': 'isKeyboardFocused',
59+
'[attr.aria-disabled]': 'disabled',
60+
},
2061
})
2162
@View({
2263
templateUrl: 'package:angular2_material/src/components/button/button.html',
2364
encapsulation: ViewEncapsulation.NONE
2465
})
25-
export class MdAnchor {
66+
export class MdAnchor extends MdButton {
2667
tabIndex: number;
2768

2869
/** Whether the component is disabled. */
29-
disabled: boolean;
70+
disabled_: boolean;
71+
72+
get disabled(): boolean {
73+
return this.disabled_;
74+
}
75+
76+
set disabled(value) {
77+
// The presence of *any* disabled value makes the component disabled, *except* for false.
78+
this.disabled_ = isPresent(value) && this.disabled !== false;
79+
}
3080

3181
onClick(event) {
3282
// A disabled anchor shouldn't navigate anywhere.
33-
if (isPresent(this.disabled) && this.disabled !== false) {
83+
if (this.disabled) {
3484
event.preventDefault();
3585
}
3686
}

modules/examples/src/material/button/demo_app.html

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@
77
position: relative !important;
88
padding-bottom: 10px;
99
}
10+
1011
.label {
1112
position: absolute;
1213
bottom: 5px;
1314
left: 7px;
1415
color: #ccc;
1516
font-size: 14px;
1617
}
18+
19+
.custom {
20+
background-color: #ae0001;
21+
color: #eeba30;
22+
}
23+
24+
.custom:hover, .custom.md-button-focus {
25+
background-color: #740001;
26+
color: #d3a625;
27+
}
1728
</style>
1829

1930
<h1>Button demo</h1>
@@ -40,6 +51,7 @@ <h1>Button demo</h1>
4051
<button md-button disabled="disabled" (^click)="click('disabled')">DISABLED</button>
4152
<button md-button class="md-accent" (^click)="click('accent')">ACCENT</button>
4253
<button md-button class="md-warn" (^click)="click('warn')">WARN</button>
54+
<button md-button class="custom" (^click)="click('custom')">CUSTOM</button>
4355
</section>
4456

4557
<section>
@@ -49,6 +61,7 @@ <h1>Button demo</h1>
4961
<button md-raised-button disabled="disabled" (^click)="click('raised disabled')">DISABLED</button>
5062
<button md-raised-button class="md-accent" (^click)="click('raised accent')">ACCENT</button>
5163
<button md-raised-button class="md-warn" (^click)="click('raised warn')">WARN</button>
64+
<button md-raised-button class="custom" (^click)="click('custom raised')">CUSTOM</button>
5265
</section>
5366
<section>
5467
<span class="label">Fab button</span>
@@ -57,6 +70,7 @@ <h1>Button demo</h1>
5770
<button md-fab disabled="disabled" (^click)="click('fab disabled')">DIS</button>
5871
<button md-fab class="md-accent" (^click)="click('fab accent')">ACC</button>
5972
<button md-fab class="md-warn" (^click)="click('fab warn')">WRN</button>
73+
<button md-fab class="custom" (^click)="click('custom fab')">CSTM</button>
6074
</section>
6175
<section>
6276
<span class="label">Anchor / hyperlink</span>
@@ -65,9 +79,10 @@ <h1>Button demo</h1>
6579
<a md-raised-button target="_blank" href="http://google.com">RAISED HREF</a>
6680
</section>
6781

82+
<section dir="rtl">
83+
<span class="label" dir="ltr">Right-to-left</span>
84+
<button md-button (^click)="click('Hebrew button')">לחצן</button>
85+
<button md-raised-button (^click)="click('Hebrew raised button')">העלה</button>
86+
<a md-button href="http://translate.google.com">עוגן</a>
87+
</section>
6888

69-
<p template="ng-for #item of items">
70-
Repeated button:
71-
<button md-button tabindex="-1" (^click)="increment()">{{action}}</button>
72-
{{clickCount}}
73-
</p>

0 commit comments

Comments
 (0)