Skip to content

feat(select): basic selection and animations #1647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -25,6 +25,10 @@ export class ListKeyManager {

constructor(private _items: QueryList<MdFocusable>) {}

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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add md-option to style-compatibility.ts

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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you reusing any of the placeholder css from md-input?

Copy link
Contributor Author

@kara kara Oct 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not directly. Since the input using CSS transitions and not the Angular animations API, it's hard to share. There wasn't much left over to copy after that. In the next PR (for forms), I can reuse more of the input CSS (for things like the underline, etc).

<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