Skip to content

Commit

Permalink
feat(select): basic selection and animations (#1647)
Browse files Browse the repository at this point in the history
  • Loading branch information
kara authored and hansl committed Nov 1, 2016
1 parent 3ee1b59 commit b14bb72
Show file tree
Hide file tree
Showing 15 changed files with 871 additions and 20 deletions.
6 changes: 5 additions & 1 deletion src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<md-select></md-select>
<div class="demo-select">
<md-select placeholder="Food">
<md-option *ngFor="let food of foods"> {{ food.viewValue }} </md-option>
</md-select>
</div>
2 changes: 2 additions & 0 deletions src/demo-app/select/select-demo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.demo-select {
}
6 changes: 5 additions & 1 deletion src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ import {Component} from '@angular/core';
styleUrls: ['select-demo.css'],
})
export class SelectDemo {

foods = [
{value: 'steak', viewValue: 'Steak'},
{value: 'pizza', viewValue: 'Pizza'},
{value: 'tacos', viewValue: 'Tacos'}
];
}
4 changes: 4 additions & 0 deletions src/lib/core/a11y/list-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export class ListKeyManager {
return this._tabOut.asObservable();
}

get focusedItemIndex(): number {
return this._focusedItemIndex;
}

set focusedItemIndex(value: number) {
this._focusedItemIndex = value;
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/core/compatibility/style-compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const ELEMENTS_SELECTOR = `
md-list-item,
md-menu,
md-nav-list,
md-option,
md-placeholder,
md-progress-bar,
md-progress-circle,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/select/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Work in progress!

The select is still a work in progress, so most features have not been implemented. Not ready for use!
45 changes: 45 additions & 0 deletions src/lib/select/_select-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,50 @@
@import '../core/theming/theming';

@mixin md-select-theme($theme) {
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
$primary: map-get($theme, primary);

.md-select-trigger {
color: md-color($foreground, hint-text);
border-bottom: 1px solid md-color($foreground, divider);

md-select:focus & {
border-bottom: 1px solid md-color($primary);
}
}

.md-select-placeholder {
md-select:focus & {
color: md-color($primary);
}
}

.md-select-arrow {
color: md-color($foreground, hint-text);

md-select:focus & {
color: md-color($primary);
}
}

.md-select-content {
background: md-color($background, card);
}

.md-select-value {
color: md-color($foreground, text);
}

md-option {
&:hover, &:focus {
background: md-color($background, hover);
}

&.md-selected {
background: md-color($background, hover);
color: md-color($primary);
}

}
}
8 changes: 6 additions & 2 deletions src/lib/select/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MdSelect} from './select';
import {MdOption} from './option';
import {OverlayModule} from '../core/overlay/overlay-directives';
import {MdRippleModule} from '../core/ripple/ripple';
import {OVERLAY_PROVIDERS} from '../core/overlay/overlay';
export * from './select';

@NgModule({
imports: [],
imports: [CommonModule, OverlayModule, MdRippleModule],
exports: [MdSelect, MdOption],
declarations: [MdSelect, MdOption],
})
export class MdSelectModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MdSelectModule,
providers: []
providers: [OVERLAY_PROVIDERS]
};
}
}
3 changes: 3 additions & 0 deletions src/lib/select/option.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<ng-content></ng-content>
<div class="md-option-ripple" md-ripple md-ripple-background-color="rgba(0,0,0,0)"
[md-ripple-trigger]="_getHostElement()"></div>
71 changes: 68 additions & 3 deletions src/lib/select/option.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,75 @@
import {Component, ViewEncapsulation} from '@angular/core';
import {
Component,
ElementRef,
EventEmitter,
Output,
Renderer,
ViewEncapsulation
} from '@angular/core';
import {ENTER, SPACE} from '../core/keyboard/keycodes';

@Component({
moduleId: module.id,
selector: 'md-option',
template: ``,
host: {
'role': 'option',
'tabindex': '0',
'[class.md-selected]': 'selected',
'[attr.aria-selected]': 'selected.toString()',
'(click)': 'select()',
'(keydown)': '_handleKeydown($event)'
},
templateUrl: 'option.html',
styleUrls: ['select.css'],
encapsulation: ViewEncapsulation.None
})
export class MdOption {}
export class MdOption {
private _selected = false;

/** Event emitted when the option is selected. */
@Output() onSelect = new EventEmitter();

constructor(private _element: ElementRef, private _renderer: Renderer) {}

/** Whether or not the option is currently selected. */
get selected(): boolean {
return this._selected;
}

/**
* The displayed value of the option. It is necessary to show the selected option in the
* select's trigger.
* TODO(kara): Add input property alternative for node envs.
*/
get viewValue(): string {
return this._getHostElement().textContent.trim();
}

/** Selects the option. */
select(): void {
this._selected = true;
this.onSelect.emit();
}

/** Deselects the option. */
deselect(): void {
this._selected = false;
}

/** Sets focus onto this option. */
focus(): void {
this._renderer.invokeElementMethod(this._getHostElement(), 'focus');
}

/** Ensures the option is selected when activated from the keyboard. */
_handleKeydown(event: KeyboardEvent): void {
if (event.keyCode === ENTER || event.keyCode === SPACE) {
this.select();
}
}

_getHostElement(): HTMLElement {
return this._element.nativeElement;
}

}
79 changes: 79 additions & 0 deletions src/lib/select/select-animations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
animate,
AnimationEntryMetadata,
state,
style,
transition,
trigger,
} from '@angular/core';

/**
* The following are all the animations for the md-select component, with each
* const containing the metadata for one animation.
*
* The values below match the implementation of the Material 1 md-select animation.
*/

/**
* This animation shrinks the placeholder text to 75% of its normal size and translates
* it to either the top left corner (ltr) or top right corner (rtl) of the trigger,
* depending on the text direction of the application.
*/
export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPlaceholder', [
state('normal', style({
transform: `translate3d(0, 0, 0) scale(1)`
})),
state('floating-ltr', style({
transform: `translate3d(-2px, -22px, 0) scale(0.75)`
})),
state('floating-rtl', style({
transform: `translate3d(2px, -22px, 0) scale(0.75)`
})),
transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`))
]);

/**
* This animation transforms the select's overlay panel on and off the page.
*
* When the panel is attached to the DOM, it expands its width 32px, scales it up to
* 100% on the Y axis, fades in its border, and translates slightly up and to the
* side to ensure the option text correctly overlaps the trigger text.
*
* When the panel is removed from the DOM, it simply fades out linearly.
*/
export const transformPanel: AnimationEntryMetadata = trigger('transformPanel', [
state('showing-ltr', style({
opacity: 1,
width: 'calc(100% + 32px)',
transform: `translate3d(-16px, -9px, 0) scaleY(1)`
})),
state('showing-rtl', style({
opacity: 1,
width: 'calc(100% + 32px)',
transform: `translate3d(16px, -9px, 0) scaleY(1)`
})),
transition('void => *', [
style({
opacity: 0,
width: '100%',
transform: `translate3d(0, 0, 0) scaleY(0)`
}),
animate(`150ms cubic-bezier(0.55, 0, 0.55, 0.2)`)
]),
transition('* => void', [
animate('250ms linear', style({opacity: 0}))
])
]);

/**
* This animation fades in the background color and text content of the
* select's options. It is time delayed to occur 100ms after the overlay
* panel has transformed in.
*/
export const fadeInContent: AnimationEntryMetadata = trigger('fadeInContent', [
state('showing', style({opacity: 1})),
transition('void => showing', [
style({opacity: 0}),
animate(`150ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)`)
])
]);
16 changes: 15 additions & 1 deletion src/lib/select/select.html
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
I'm a select!
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span>
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
<span class="md-select-arrow"></span>
</div>

<template connected-overlay [origin]="origin" [open]="panelOpen" hasBackdrop (backdropClick)="close()"
backdropClass="md-overlay-transparent-backdrop" [positions]="_positions" [width]="_getWidth()">
<div class="md-select-panel" [@transformPanel]="_getPanelState()" (@transformPanel.done)="_onPanelDone()"
(keydown)="_keyManager.onKeydown($event)">
<div class="md-select-content" [@fadeInContent]="'showing'">
<ng-content></ng-content>
</div>
</div>
</template>
62 changes: 62 additions & 0 deletions src/lib/select/select.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@import '../core/style/menu-common';

$md-select-trigger-height: 30px !default;
$md-select-trigger-min-width: 112px !default;
$md-select-arrow-size: 5px !default;

md-select {
display: inline-block;
outline: none;
}

.md-select-trigger {
display: flex;
justify-content: space-between;
align-items: center;
height: $md-select-trigger-height;
min-width: $md-select-trigger-min-width;
cursor: pointer;
}

.md-select-placeholder {
padding: 0 2px;
transform-origin: left top;

[dir='rtl'] & {
transform-origin: right top;
}
}

.md-select-value {
position: absolute;
}

.md-select-arrow {
width: 0;
height: 0;
border-left: $md-select-arrow-size solid transparent;
border-right: $md-select-arrow-size solid transparent;
border-top: $md-select-arrow-size solid;
}

.md-select-panel {
@include md-menu-base();
padding-top: 0;
padding-bottom: 0;
transform-origin: top;
}

md-option {
@include md-menu-item-base();
position: relative;
cursor: pointer;
outline: none;
}

.md-option-ripple {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
Loading

0 comments on commit b14bb72

Please sign in to comment.