diff --git a/commitlint.config.js b/commitlint.config.js index baad2b20b..04e99e16a 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -29,6 +29,7 @@ module.exports = { 'list', 'modal', 'navbar', + 'optgroup', 'panel', 'popover', 'progress-bar', diff --git a/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.css b/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.css new file mode 100644 index 000000000..743230875 --- /dev/null +++ b/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.html b/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.html new file mode 100644 index 000000000..19a28c864 --- /dev/null +++ b/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.html @@ -0,0 +1,8 @@ + + + + {{ pokemon.viewValue }} + + + Mr. Mime + diff --git a/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.ts b/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.ts new file mode 100644 index 000000000..a7ec5d78f --- /dev/null +++ b/packages/mosaic-examples/mosaic/list/list-groups/list-groups-example.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; + + +/** + * @title Basic list + */ +@Component({ + selector: 'list-groups-example', + templateUrl: 'list-groups-example.html', + styleUrls: ['list-groups-example.css'] +}) +export class ListGroupsExample { + pokemonTypes = [ + { + name: 'Grass', + pokemon: [ + { value: 'bulbasaur-0', viewValue: 'Bulbasaur' }, + { value: 'oddish-1', viewValue: 'Oddish' }, + { value: 'bellsprout-2', viewValue: 'Bellsprout' } + ] + }, + { + name: 'Water', + disabled: true, + pokemon: [ + { value: 'squirtle-3', viewValue: 'Squirtle' }, + { value: 'psyduck-4', viewValue: 'Psyduck' }, + { value: 'horsea-5', viewValue: 'Horsea' } + ] + }, + { + name: 'Fire', + 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' } + ] + } + ]; +} diff --git a/packages/mosaic-examples/mosaic/list/module.ts b/packages/mosaic-examples/mosaic/list/module.ts index cb45278dc..2d3544d66 100644 --- a/packages/mosaic-examples/mosaic/list/module.ts +++ b/packages/mosaic-examples/mosaic/list/module.ts @@ -1,7 +1,9 @@ +import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { McListModule } from '@ptsecurity/mosaic/list'; +import { ListGroupsExample } from './list-groups/list-groups-example'; import { ListMultipleCheckboxExample } from './list-multiple-checkbox/list-multiple-checkbox-example'; import { ListMultipleKeyboardExample } from './list-multiple-keyboard/list-multiple-keyboard-example'; import { ListOverviewExample } from './list-overview/list-overview-example'; @@ -10,11 +12,13 @@ import { ListOverviewExample } from './list-overview/list-overview-example'; const EXAMPLES = [ ListOverviewExample, ListMultipleCheckboxExample, - ListMultipleKeyboardExample + ListMultipleKeyboardExample, + ListGroupsExample ]; @NgModule({ imports: [ + CommonModule, FormsModule, McListModule ], diff --git a/packages/mosaic-examples/mosaic/select/module.ts b/packages/mosaic-examples/mosaic/select/module.ts index 752db6703..9a96874c7 100644 --- a/packages/mosaic-examples/mosaic/select/module.ts +++ b/packages/mosaic-examples/mosaic/select/module.ts @@ -5,6 +5,7 @@ import { McIconModule } from '@ptsecurity/mosaic/icon'; import { McInputModule } from '@ptsecurity/mosaic/input'; import { McSelectModule } from '@ptsecurity/mosaic/select'; +import { SelectGroupsExample } from './select-groups/select-groups-example'; import { SelectMultipleOverviewExample } from './select-multiple-overview/select-multiple-overview-example'; import { SelectOverviewExample } from './select-overview/select-overview-example'; import { SelectSearchOverviewExample } from './select-search-overview/select-search-overview-example'; @@ -13,7 +14,8 @@ import { SelectSearchOverviewExample } from './select-search-overview/select-sea const EXAMPLES = [ SelectOverviewExample, SelectMultipleOverviewExample, - SelectSearchOverviewExample + SelectSearchOverviewExample, + SelectGroupsExample ]; @NgModule({ diff --git a/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.css b/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.css new file mode 100644 index 000000000..743230875 --- /dev/null +++ b/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.html b/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.html new file mode 100644 index 000000000..1b78f0d65 --- /dev/null +++ b/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.html @@ -0,0 +1,8 @@ + + + + {{ pokemon.viewValue }} + + Mr. Mime + + diff --git a/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.ts b/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.ts new file mode 100644 index 000000000..45b854027 --- /dev/null +++ b/packages/mosaic-examples/mosaic/select/select-groups/select-groups-example.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; + + +/** + * @title Basic Select + */ +@Component({ + selector: 'select-groups-example', + templateUrl: 'select-groups-example.html', + styleUrls: ['select-groups-example.css'] +}) +export class SelectGroupsExample { + pokemonTypes = [ + { + name: 'Grass', + pokemon: [ + { value: 'bulbasaur-0', viewValue: 'Bulbasaur' }, + { value: 'oddish-1', viewValue: 'Oddish' }, + { value: 'bellsprout-2', viewValue: 'Bellsprout' } + ] + }, + { + name: 'Water', + disabled: true, + pokemon: [ + { value: 'squirtle-3', viewValue: 'Squirtle' }, + { value: 'psyduck-4', viewValue: 'Psyduck' }, + { value: 'horsea-5', viewValue: 'Horsea' } + ] + }, + { + name: 'Fire', + 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' } + ] + } + ]; +} diff --git a/packages/mosaic/core/option/_optgroup-theme.scss b/packages/mosaic/core/option/_optgroup-theme.scss index 4948b5a9f..7c401a5d8 100644 --- a/packages/mosaic/core/option/_optgroup-theme.scss +++ b/packages/mosaic/core/option/_optgroup-theme.scss @@ -10,13 +10,13 @@ color: mc-color($foreground, secondary-text); } - .mc-optgroup-disabled .mc-optgroup-label { - color: mc-color($foreground, hint-text); + .mc-disabled .mc-optgroup-label { + color: mc-color($foreground, disabled-text); } } @mixin mc-optgroup-typography($config) { .mc-optgroup-label { - @include mc-typography-level-to-styles($config, body-2); + @include mc-typography-level-to-styles($config, subheading); } } diff --git a/packages/mosaic/core/option/optgroup.html b/packages/mosaic/core/option/optgroup.html index 7353d5f24..681eabaf6 100644 --- a/packages/mosaic/core/option/optgroup.html +++ b/packages/mosaic/core/option/optgroup.html @@ -1,2 +1,2 @@ - + diff --git a/packages/mosaic/core/option/optgroup.scss b/packages/mosaic/core/option/optgroup.scss index f58b4f43a..e9472c2d1 100644 --- a/packages/mosaic/core/option/optgroup.scss +++ b/packages/mosaic/core/option/optgroup.scss @@ -2,6 +2,9 @@ .mc-optgroup-label { + padding-left: 16px; + @include user-select(none); + cursor: default; } diff --git a/packages/mosaic/core/option/optgroup.ts b/packages/mosaic/core/option/optgroup.ts index 2a19a3e57..113c9ff2d 100644 --- a/packages/mosaic/core/option/optgroup.ts +++ b/packages/mosaic/core/option/optgroup.ts @@ -3,14 +3,12 @@ import { Component, ViewEncapsulation, Input, ChangeDetectionStrategy } from '@a import { mixinDisabled, CanDisable, CanDisableCtor } from '../common-behaviors/index'; -// Boilerplate for applying mixins to McOptgroup. /** @docs-private */ export class McOptgroupBase {} // tslint:disable-next-line: naming-convention export const McOptgroupMixinBase: CanDisableCtor & typeof McOptgroupBase = mixinDisabled(McOptgroupBase); -// Counter for unique group ids. let uniqueOptgroupIdCounter = 0; /** @@ -20,20 +18,16 @@ let uniqueOptgroupIdCounter = 0; selector: 'mc-optgroup', exportAs: 'mcOptgroup', templateUrl: 'optgroup.html', + styleUrls: ['./optgroup.css'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, inputs: ['disabled'], - styleUrls: ['optgroup.css'], host: { class: 'mc-optgroup', - role: 'group', - '[class.mc-optgroup-disabled]': 'disabled', - '[attr.aria-disabled]': 'disabled.toString()', - '[attr.aria-labelledby]': 'labelId' + '[class.mc-disabled]': 'disabled' } }) export class McOptgroup extends McOptgroupMixinBase implements CanDisable { - /** Label for the option group. */ @Input() label: string; /** Unique id for the underlying label. */ diff --git a/packages/mosaic/core/styles/typography/_all-typography.scss b/packages/mosaic/core/styles/typography/_all-typography.scss index 33a3f47fa..27ff28df6 100644 --- a/packages/mosaic/core/styles/typography/_all-typography.scss +++ b/packages/mosaic/core/styles/typography/_all-typography.scss @@ -26,6 +26,7 @@ @import '../../../tree/tree-theme'; @import '../../../vertical-navbar/vertical-navbar-theme'; @import '../../option/option-theme'; +@import '../../option/optgroup-theme'; @mixin mosaic-typography($config: null) { @@ -50,6 +51,7 @@ @include mc-modal-typography($config); @include mc-navbar-typography($config); @include mc-option-typography($config); + @include mc-optgroup-typography($config); @include mc-popover-typography($config); @include mc-radio-typography($config); @include mc-select-typography($config); diff --git a/packages/mosaic/core/theming/_all-theme.scss b/packages/mosaic/core/theming/_all-theme.scss index 51748c9a4..50b24c7c9 100644 --- a/packages/mosaic/core/theming/_all-theme.scss +++ b/packages/mosaic/core/theming/_all-theme.scss @@ -33,6 +33,7 @@ @import '../../tree/tree-theme'; @import '../../vertical-navbar/vertical-navbar-theme'; @import '../option/option-theme'; +@import '../option/optgroup-theme'; @import '../highlight/highlight-theme'; @import '../visual/panel-theme'; @@ -57,6 +58,7 @@ @include mc-modal-theme($theme); @include mc-navbar-theme($theme); @include mc-option-theme($theme); + @include mc-optgroup-theme($theme); @include mc-panel-theme($theme); @include mc-popover-theme($theme); @include mc-progress-bar-theme($theme); diff --git a/packages/mosaic/list/_list-theme.scss b/packages/mosaic/list/_list-theme.scss index 5424007f5..8cc99b247 100644 --- a/packages/mosaic/list/_list-theme.scss +++ b/packages/mosaic/list/_list-theme.scss @@ -45,6 +45,7 @@ background: mc-color($primary, if($is-dark-theme, 700, 100)); } + &.mc-disabled, &[disabled] { background: transparent; diff --git a/packages/mosaic/list/list-selection.component.ts b/packages/mosaic/list/list-selection.component.ts index 806484e38..1b4d0962e 100644 --- a/packages/mosaic/list/list-selection.component.ts +++ b/packages/mosaic/list/list-selection.component.ts @@ -19,7 +19,8 @@ import { OnDestroy, OnInit, ViewChild, - NgZone + NgZone, + Optional } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FocusKeyManager, IFocusableOption } from '@ptsecurity/cdk/a11y'; @@ -44,7 +45,8 @@ import { HasTabIndexCtor, mixinTabIndex, HasTabIndex, - MultipleMode + MultipleMode, + McOptgroup } from '@ptsecurity/mosaic/core'; import { merge, Observable, Subject, Subscription } from 'rxjs'; import { startWith, take, takeUntil } from 'rxjs/operators'; @@ -69,6 +71,7 @@ export interface McOptionEvent { class: 'mc-list-option', '[class.mc-selected]': 'selected', '[class.mc-focused]': 'hasFocus', + '[class.mc-disabled]': 'disabled', '(focus)': 'focus()', '(blur)': 'blur()', @@ -97,7 +100,8 @@ export class McListOption implements OnDestroy, OnInit, IFocusableOption { @Input() get disabled() { - return this._disabled || (this.listSelection && this.listSelection.disabled); + return (this.listSelection && this.listSelection.disabled) || (this.group && this.group.disabled) || + this._disabled; } set disabled(value: any) { @@ -147,7 +151,8 @@ export class McListOption implements OnDestroy, OnInit, IFocusableOption { private elementRef: ElementRef, private changeDetector: ChangeDetectorRef, private ngZone: NgZone, - @Inject(forwardRef(() => McListSelection)) public listSelection: McListSelection + @Inject(forwardRef(() => McListSelection)) public listSelection: McListSelection, + @Optional() readonly group: McOptgroup ) {} ngOnInit() { @@ -290,8 +295,7 @@ export class McListSelection extends McListSelectionMixinBase implements CanDisa keyManager: FocusKeyManager; - // The option components contained within this selection-list. - @ContentChildren(McListOption) options: QueryList; + @ContentChildren(McListOption, { descendants: true }) options: QueryList; autoSelect: boolean; noUnselect: boolean; diff --git a/packages/mosaic/list/list.md b/packages/mosaic/list/list.md index ead288d0e..f00bde9ba 100644 --- a/packages/mosaic/list/list.md +++ b/packages/mosaic/list/list.md @@ -2,9 +2,13 @@ -### Multiple mode with checkboxes +#### Multiple mode with checkboxes -### Multiple mode without checkboxes - \ No newline at end of file +#### Multiple mode without checkboxes + + + +#### Multiple mode without checkboxes + \ No newline at end of file diff --git a/packages/mosaic/list/list.module.ts b/packages/mosaic/list/list.module.ts index 7039ecfff..74a6e6f23 100644 --- a/packages/mosaic/list/list.module.ts +++ b/packages/mosaic/list/list.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { A11yModule } from '@ptsecurity/cdk/a11y'; -import { McLineModule, McPseudoCheckboxModule } from '@ptsecurity/mosaic/core'; +import { McLineModule, McOptionModule, McPseudoCheckboxModule } from '@ptsecurity/mosaic/core'; import { McListSelection, McListOption } from './list-selection.component'; import { McList, McListItem, McListSubheaderCssStyler } from './list.component'; @@ -12,13 +12,15 @@ import { McList, McListItem, McListSubheaderCssStyler } from './list.component'; CommonModule, A11yModule, McPseudoCheckboxModule, - McLineModule + McLineModule, + McOptionModule ], exports: [ McList, McListSelection, McListItem, McListOption, + McOptionModule, McListSubheaderCssStyler ], declarations: [ diff --git a/packages/mosaic/list/list.scss b/packages/mosaic/list/list.scss index 4095ece42..b15221333 100644 --- a/packages/mosaic/list/list.scss +++ b/packages/mosaic/list/list.scss @@ -45,6 +45,6 @@ } } -.mc-list-option:not([disabled]) { +.mc-list-option:not([disabled]):not(.mc-disabled){ cursor: pointer; } diff --git a/packages/mosaic/select/select.component.spec.ts b/packages/mosaic/select/select.component.spec.ts index 30d3d5a7c..5b13af20c 100644 --- a/packages/mosaic/select/select.component.spec.ts +++ b/packages/mosaic/select/select.component.spec.ts @@ -526,8 +526,7 @@ class FalsyValueSelect { template: ` - + {{ pokemon.viewValue }} @@ -2187,7 +2186,7 @@ describe('McSelect', () => { expect(panel.scrollTop).toBe(288, 'Expected scroll to be at the 9th option.'); })); - xit('should skip option group labels', fakeAsync(() => { + it('should skip option group labels', fakeAsync(() => { fixture.destroy(); const groupFixture = TestBed.createComponent(SelectWithGroups); @@ -2198,7 +2197,7 @@ describe('McSelect', () => { flush(); host = groupFixture.debugElement.query(By.css('mc-select')).nativeElement; - panel = overlayContainerElement.querySelector('.mc-select__panel') as HTMLElement; + panel = overlayContainerElement.querySelector('.mc-select__content') as HTMLElement; for (let i = 0; i < 5; i++) { dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW); @@ -2206,8 +2205,8 @@ describe('McSelect', () => { // Note that we press down 5 times, but it will skip // 3 options because the second group is disabled. - // <(option index + group labels) * height> - = (9 + 3) * 48 - 256 = 320 - expect(panel.scrollTop).toBe(320, 'Expected scroll to be at the 9th option.'); + // <(option index + group labels) * height> - = (9 + 3) * 32 - 224 = 160 + expect(panel.scrollTop).toBe(160, 'Expected scroll to be at the 9th option.'); })); it('should scroll top the top when pressing HOME', fakeAsync(() => { @@ -3403,7 +3402,7 @@ describe('McSelect', () => { groupFixture.detectChanges(); flush(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mc-select__panel')!; + const scrollContainer = document.querySelector('.cdk-overlay-pane .mc-select__content')!; // The selected option should be scrolled to the center of the panel. // This will be its original offset from the scrollTop - half the panel height + half the diff --git a/packages/mosaic/select/select.md b/packages/mosaic/select/select.md index 858a5b04a..25619e00d 100644 --- a/packages/mosaic/select/select.md +++ b/packages/mosaic/select/select.md @@ -6,3 +6,6 @@ #### With search + +#### With groups +