Skip to content

Commit

Permalink
feat(select): add md-optgroup component
Browse files Browse the repository at this point in the history
Adds the `md-optgroup` component, which can be used to group options inside of `md-select`, in a similar way to the native `optgroup`.

Fixes #3182.
  • Loading branch information
crisbeto committed May 8, 2017
1 parent 525ce1e commit 6369c1b
Show file tree
Hide file tree
Showing 14 changed files with 433 additions and 41 deletions.
19 changes: 18 additions & 1 deletion src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div style="height: 1000px">This div is for testing scrolled selects.</div>
Space above cards: <input type="number" [formControl]="topHeightCtrl">
<button md-button (click)="showSelect=!showSelect">SHOW SELECT</button>
<div [style.height.px]="topHeightCtrl.value"></div>

<div class="demo-select">
<md-card>
<md-card-subtitle>ngModel</md-card-subtitle>
Expand Down Expand Up @@ -62,6 +64,21 @@
</md-card-content>
</md-card>

<md-card>
<md-card-subtitle>Option groups</md-card-subtitle>

<md-card-content>
<md-select placeholder="Pokemon" [(ngModel)]="currentPokemonFromGroup">
<md-optgroup *ngFor="let group of pokemonGroups" [label]="group.name"
[disabled]="group.disabled">
<md-option *ngFor="let creature of group.pokemon" [value]="creature.value">
{{ creature.viewValue }}
</md-option>
</md-optgroup>
</md-select>
</md-card-content>
</md-card>

<div *ngIf="showSelect">
<md-card>
<md-card-subtitle>formControl</md-card-subtitle>
Expand Down
37 changes: 37 additions & 0 deletions src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export class SelectDemo {
showSelect = false;
currentDrink: string;
currentPokemon: string[];
currentPokemonFromGroup: string;
latestChangeEvent: MdSelectChange;
floatPlaceholder: string = 'auto';
foodControl = new FormControl('pizza-1');
topHeightCtrl = new FormControl(0);
drinksTheme = 'primary';
pokemonTheme = 'primary';

Expand Down Expand Up @@ -56,6 +58,41 @@ export class SelectDemo {
{value: 'warn', name: 'Warn' }
];

pokemonGroups = [
{
name: 'Grass',
pokemon: [
{ value: 'bulbasaur-0', viewValue: 'Bulbasaur' },
{ value: 'oddish-1', viewValue: 'Oddish' },
{ value: 'bellsprout-2', viewValue: 'Bellsprout' }
]
},
{
name: 'Water',
pokemon: [
{ value: 'squirtle-3', viewValue: 'Squirtle' },
{ value: 'psyduck-4', viewValue: 'Psyduck' },
{ value: 'horsea-5', viewValue: 'Horsea' }
]
},
{
name: 'Fire',
disabled: true,
pokemon: [
{ value: 'charmander-6', viewValue: 'Charmander' },
{ value: 'vulpix-7', viewValue: 'Vulpix' },
{ value: 'flareon-8', viewValue: 'Flareon' }
]
},
{
name: 'Psychic',
pokemon: [
{ value: 'mew-9', viewValue: 'Mew' },
{ value: 'mewtwo-10', viewValue: 'Mewtwo' },
]
}
];

toggleDisabled() {
this.foodControl.enabled ? this.foodControl.disable() : this.foodControl.enable();
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/core/_core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
@import 'ripple/ripple';
@import 'option/option';
@import 'option/option-theme';
@import 'option/optgroup';
@import 'option/optgroup-theme';
@import 'selection/pseudo-checkbox/pseudo-checkbox-theme';

// Mixin that renders all of the core styles that are not theme-dependent.
Expand All @@ -20,6 +22,7 @@

@include mat-ripple();
@include mat-option();
@include mat-optgroup();
@include cdk-a11y();
@include cdk-overlay();
}
Expand All @@ -28,6 +31,7 @@
@mixin mat-core-theme($theme) {
@include mat-ripple-theme($theme);
@include mat-option-theme($theme);
@include mat-optgroup-theme($theme);
@include mat-pseudo-checkbox-theme($theme);

// Wrapper element that provides the theme background when the
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {NgModule} from '@angular/core';
import {MdLineModule} from './line/line';
import {RtlModule} from './rtl/dir';
import {ObserveContentModule} from './observe-content/observe-content';
import {MdOptionModule} from './option/option';
import {MdOptionModule} from './option/index';
import {PortalModule} from './portal/portal-directives';
import {OverlayModule} from './overlay/overlay-directives';
import {A11yModule} from './a11y/index';
Expand All @@ -16,7 +16,7 @@ export {Dir, LayoutDirection, RtlModule} from './rtl/dir';
// Mutation Observer
export {ObserveContentModule, ObserveContent} from './observe-content/observe-content';

export {MdOptionModule, MdOption, MdOptionSelectionChange} from './option/option';
export * from './option/index';

// Portals
export {
Expand Down
14 changes: 14 additions & 0 deletions src/lib/core/option/_optgroup-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import '../theming/palette';
@import '../theming/theming';

@mixin mat-optgroup-theme($theme) {
$foreground: map-get($theme, foreground);

.mat-optgroup-label {
color: mat-color($foreground, secondary-text);
}

.mat-optgroup-disabled .mat-optgroup-label {
color: mat-color($foreground, hint-text);
}
}
14 changes: 14 additions & 0 deletions src/lib/core/option/_optgroup.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import '../style/menu-common';
@import '../style/vendor-prefixes';

@mixin mat-optgroup() {
.mat-optgroup-label {
@include mat-menu-item-base();
@include user-select(none);
cursor: default;

// TODO(crisbeto): should use the typography functions once #4375 is in.
font-weight: bold;
font-size: 14px;
}
}
11 changes: 10 additions & 1 deletion src/lib/core/option/_option.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
@include user-select(none);
cursor: default;
}

.mat-optgroup &:not(.mat-option-multiple) {
padding-left: $mat-menu-side-padding * 2;

[dir='rtl'] & {
padding-left: $mat-menu-side-padding;
padding-right: $mat-menu-side-padding * 2;
}
}
}

.mat-option-ripple {
Expand All @@ -26,7 +35,7 @@
bottom: 0;
right: 0;

// In high contrast mode this completely covers the text.
// Prevents the ripple from completely covering the option in high contrast mode.
@include cdk-high-contrast {
opacity: 0.5;
}
Expand Down
18 changes: 18 additions & 0 deletions src/lib/core/option/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MdRippleModule} from '../ripple/index';
import {MdSelectionModule} from '../selection/index';
import {MdOption} from './option';
import {MdOptgroup} from './optgroup';


@NgModule({
imports: [MdRippleModule, CommonModule, MdSelectionModule],
exports: [MdOption, MdOptgroup],
declarations: [MdOption, MdOptgroup]
})
export class MdOptionModule {}


export * from './option';
export * from './optgroup';
2 changes: 2 additions & 0 deletions src/lib/core/option/optgroup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<label class="mat-optgroup-label" [id]="_labelId">{{ label }}</label>
<ng-content select="md-option, mat-option"></ng-content>
33 changes: 33 additions & 0 deletions src/lib/core/option/optgroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Component, ViewEncapsulation, ContentChildren, QueryList, Input} from '@angular/core';
import {mixinDisabled, CanDisable} from '../common-behaviors/disabled';

// Boilerplate for applying mixins to MdOptgroup.
export class MdOptgroupBase { }
export const _MdOptgroupMixinBase = mixinDisabled(MdOptgroupBase);

// Counter for unique group ids.
let nextId = 0;

/**
* Component that is used to group instances of `md-option`.
*/
@Component({
moduleId: module.id,
selector: 'md-optgroup, mat-optgroup',
templateUrl: 'optgroup.html',
encapsulation: ViewEncapsulation.None,
inputs: ['disabled'],
host: {
'class': 'mat-optgroup',
'[class.mat-optgroup-disabled]': 'disabled',
'[attr.aria-disabled]': 'disabled.toString()',
'[attr.aria-labelledby]': '_labelId',
}
})
export class MdOptgroup extends _MdOptgroupMixinBase implements CanDisable {
/** Label for the option group. */
@Input() label: string;

/** Unique id for the underlying label. */
_labelId: string = `mat-optgroup-label-${nextId++}`;
}
14 changes: 3 additions & 11 deletions src/lib/core/option/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ import {
Inject,
Optional,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ENTER, SPACE} from '../keyboard/keycodes';
import {coerceBooleanProperty} from '../coercion/boolean-property';
import {MdRippleModule} from '../ripple/index';
import {MdSelectionModule} from '../selection/index';
import {MATERIAL_COMPATIBILITY_MODE} from '../../core/compatibility/compatibility';
import {MdOptgroup} from './optgroup';

/**
* Option IDs need to be unique across components, so this counter exists outside of
Expand Down Expand Up @@ -74,14 +72,15 @@ export class MdOption {

/** Whether the option is disabled. */
@Input()
get disabled() { return this._disabled; }
get disabled() { return (this.group && this.group.disabled) || this._disabled; }
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }

/** Event emitted when the option is selected or deselected. */
@Output() onSelectionChange = new EventEmitter<MdOptionSelectionChange>();

constructor(
private _element: ElementRef,
@Optional() public readonly group: MdOptgroup,
@Optional() @Inject(MATERIAL_COMPATIBILITY_MODE) public _isCompatibilityMode: boolean) {}

/**
Expand Down Expand Up @@ -172,10 +171,3 @@ export class MdOption {
}

}

@NgModule({
imports: [MdRippleModule, CommonModule, MdSelectionModule],
exports: [MdOption],
declarations: [MdOption]
})
export class MdOptionModule {}
3 changes: 1 addition & 2 deletions src/lib/select/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MdSelect} from './select';
import {MdOptionModule} from '../core/option/option';
import {MdCommonModule, OverlayModule} from '../core';
import {MdCommonModule, OverlayModule, MdOptionModule} from '../core';


@NgModule({
Expand Down
Loading

0 comments on commit 6369c1b

Please sign in to comment.