Skip to content

Commit e3c0af6

Browse files
committed
feat(select): basic selection and animations
1 parent 04e2201 commit e3c0af6

File tree

12 files changed

+763
-20
lines changed

12 files changed

+763
-20
lines changed

src/demo-app/select/select-demo.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
<md-select></md-select>
1+
<div class="demo-select">
2+
<md-select placeholder="Food">
3+
<md-option *ngFor="let food of foods"> {{ food.viewValue }} </md-option>
4+
</md-select>
5+
</div>

src/demo-app/select/select-demo.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.demo-select {
2+
}

src/demo-app/select/select-demo.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ import {Component} from '@angular/core';
88
styleUrls: ['select-demo.css'],
99
})
1010
export class SelectDemo {
11-
11+
foods = [
12+
{value: 'steak', viewValue: 'Steak'},
13+
{value: 'pizza', viewValue: 'Pizza'},
14+
{value: 'tacos', viewValue: 'Tacos'}
15+
];
1216
}

src/lib/select/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Work in progress!
2+
3+
The select is still a work in progress, so most features have not been implemented. Not ready for use!

src/lib/select/_select-theme.scss

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,50 @@
22
@import '../core/theming/theming';
33

44
@mixin md-select-theme($theme) {
5+
$foreground: map-get($theme, foreground);
6+
$background: map-get($theme, background);
7+
$primary: map-get($theme, primary);
58

9+
.md-select-trigger {
10+
color: md-color($foreground, hint-text);
11+
border-bottom: 1px solid md-color($foreground, divider);
12+
13+
md-select:focus & {
14+
border-bottom: 1px solid md-color($primary);
15+
}
16+
}
17+
18+
.md-select-placeholder {
19+
md-select:focus & {
20+
color: md-color($primary);
21+
}
22+
}
23+
24+
.md-select-arrow {
25+
color: md-color($foreground, hint-text);
26+
27+
md-select:focus & {
28+
color: md-color($primary);
29+
}
30+
}
31+
32+
.md-select-content {
33+
background: md-color($background, card);
34+
}
35+
36+
.md-select-value {
37+
color: md-color($foreground, text);
38+
}
39+
40+
md-option {
41+
&:hover, &:focus {
42+
background: md-color($background, hover);
43+
}
44+
45+
&.md-selected {
46+
background: md-color($background, hover);
47+
color: md-color($primary);
48+
}
49+
50+
}
651
}

src/lib/select/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import {NgModule, ModuleWithProviders} from '@angular/core';
2+
import {CommonModule} from '@angular/common';
23
import {MdSelect} from './select';
34
import {MdOption} from './option';
5+
import {OverlayModule} from '../core/overlay/overlay-directives';
6+
import {MdRippleModule} from '../core/ripple/ripple';
7+
import {OVERLAY_PROVIDERS} from '../core/overlay/overlay';
48
export * from './select';
59

610
@NgModule({
7-
imports: [],
11+
imports: [CommonModule, OverlayModule, MdRippleModule],
812
exports: [MdSelect, MdOption],
913
declarations: [MdSelect, MdOption],
1014
})
1115
export class MdSelectModule {
1216
static forRoot(): ModuleWithProviders {
1317
return {
1418
ngModule: MdSelectModule,
15-
providers: []
19+
providers: [OVERLAY_PROVIDERS]
1620
};
1721
}
1822
}

src/lib/select/option.ts

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,82 @@
1-
import {Component, ViewEncapsulation} from '@angular/core';
1+
import {
2+
Component,
3+
ElementRef,
4+
EventEmitter,
5+
Output,
6+
Renderer,
7+
ViewEncapsulation
8+
} from '@angular/core';
9+
import {ENTER, SPACE} from '../core/keyboard/keycodes';
210

311
@Component({
412
moduleId: module.id,
513
selector: 'md-option',
6-
template: ``,
14+
host: {
15+
'role': 'option',
16+
'[class.md-selected]': 'selected',
17+
'[attr.aria-selected]': 'selected.toString()',
18+
'[attr.tabindex]': '0',
19+
'(click)': 'select()',
20+
'(keydown)': '_selectOnActivate($event)'
21+
},
22+
template: `
23+
<ng-content></ng-content>
24+
<div class="md-option-ripple" md-ripple md-ripple-background-color="rgba(0,0,0,0)"
25+
[md-ripple-trigger]="_getHostElement()"></div>
26+
`,
727
styleUrls: ['select.css'],
828
encapsulation: ViewEncapsulation.None
929
})
10-
export class MdOption {}
30+
export class MdOption {
31+
private _selected = false;
32+
33+
/** Event emitted when the option is selected. */
34+
@Output() onSelect = new EventEmitter();
35+
36+
constructor(private _element: ElementRef, private _renderer: Renderer) {}
37+
38+
/** Whether or not the option is currently selected. */
39+
get selected() {
40+
return this._selected;
41+
}
42+
43+
/**
44+
* The displayed value of the option.
45+
* TODO(kara): Add input property alternative for node envs.
46+
*/
47+
get viewValue(): string {
48+
return this._getHostElement().textContent.trim();
49+
}
50+
51+
/** Selects the option. */
52+
select() {
53+
this._selected = true;
54+
this.onSelect.emit();
55+
}
56+
57+
/** Unselects the option. */
58+
unselect() {
59+
this._selected = false;
60+
}
61+
62+
/** Sets focus onto this option. */
63+
focus() {
64+
this._renderer.invokeElementMethod(this._getHostElement(), 'focus');
65+
}
66+
67+
/**
68+
* Ensures the option is selected when activated from the keyboard.
69+
* TODO: internal
70+
*/
71+
_selectOnActivate(event: KeyboardEvent) {
72+
if (event.keyCode === ENTER || event.keyCode === SPACE) {
73+
this.select();
74+
}
75+
}
76+
77+
// TODO: internal
78+
_getHostElement(): HTMLElement {
79+
return this._element.nativeElement;
80+
}
81+
82+
}

src/lib/select/select-animations.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
animate,
3+
AnimationEntryMetadata,
4+
state,
5+
style,
6+
transition,
7+
trigger,
8+
} from '@angular/core';
9+
10+
export class SelectAnimations {
11+
static transformPlaceholder(): AnimationEntryMetadata {
12+
return trigger('transformPlaceholder', [
13+
state('normal', style({
14+
transform: `translate3d(0, 0, 0) scale(1)`
15+
})),
16+
state('floating', style({
17+
transform: `translate3d(-2px, -22px, 0) scale(0.75)`
18+
})),
19+
state('floating-rtl', style({
20+
transform: `translate3d(2px, -22px, 0) scale(0.75)`
21+
})),
22+
transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`))
23+
]);
24+
}
25+
26+
static transformPanel(): AnimationEntryMetadata {
27+
return trigger('transformPanel', [
28+
state('in', style({
29+
opacity: 1,
30+
width: 'calc(100% + 32px)',
31+
transform: `translate3d(-16px, -9px, 0) scaleY(1)`
32+
})),
33+
state('in-rtl', style({
34+
opacity: 1,
35+
width: 'calc(100% + 32px)',
36+
transform: `translate3d(16px, -9px, 0) scaleY(1)`
37+
})),
38+
transition('void => *', [
39+
style({
40+
opacity: 0,
41+
width: 'calc(100%)',
42+
transform: `translate3d(0, 0, 0) scaleY(0)`
43+
}),
44+
animate(`150ms cubic-bezier(0.55, 0, 0.55, 0.2)`)
45+
]),
46+
transition('* => void', [
47+
animate('250ms linear', style({opacity: 0}))
48+
])
49+
]);
50+
}
51+
52+
static fadeInContent(): AnimationEntryMetadata {
53+
return trigger('fadeInContent', [
54+
state('in', style({opacity: 1})),
55+
transition('void => in', [
56+
style({opacity: 0}),
57+
animate(`150ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)`)
58+
])
59+
]);
60+
}
61+
}

src/lib/select/select.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1-
I'm a select!
1+
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
2+
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span>
3+
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
4+
<span class="md-select-arrow"></span>
5+
</div>
6+
7+
<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
8+
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()">
9+
<div class="md-select-panel" [@transformPanel]="_getPanelState()" (@transformPanel.done)="_onPanelDone()"
10+
(keydown)="_keyManager.onKeydown($event)">
11+
<div class="md-select-content" [@fadeInContent]="'in'">
12+
<ng-content></ng-content>
13+
</div>
14+
</div>
15+
</template>

src/lib/select/select.scss

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@import '../core/style/menu-common';
2+
3+
$md-select-trigger-height: 30px !default;
4+
$md-select-trigger-min-width: 112px !default;
5+
$md-select-arrow-size: 5px !default;
6+
7+
md-select {
8+
display: inline-block;
9+
outline: none;
10+
}
11+
12+
.md-select-trigger {
13+
display: flex;
14+
justify-content: space-between;
15+
align-items: center;
16+
height: $md-select-trigger-height;
17+
min-width: $md-select-trigger-min-width;
18+
cursor: pointer;
19+
}
20+
21+
.md-select-placeholder {
22+
padding: 0 2px;
23+
transform-origin: left top;
24+
25+
[dir='rtl'] & {
26+
transform-origin: right top;
27+
}
28+
}
29+
30+
.md-select-value {
31+
position: absolute;
32+
}
33+
34+
.md-select-arrow {
35+
width: 0;
36+
height: 0;
37+
border-left: $md-select-arrow-size solid transparent;
38+
border-right: $md-select-arrow-size solid transparent;
39+
border-top: $md-select-arrow-size solid;
40+
}
41+
42+
.md-select-panel {
43+
@include md-menu-base();
44+
padding-top: 0;
45+
padding-bottom: 0;
46+
transform-origin: top;
47+
}
48+
49+
md-option {
50+
@include md-menu-item-base();
51+
position: relative;
52+
cursor: pointer;
53+
outline: none;
54+
}
55+
56+
.md-option-ripple {
57+
position: absolute;
58+
top: 0;
59+
left: 0;
60+
bottom: 0;
61+
right: 0;
62+
}

0 commit comments

Comments
 (0)