From 76219407070cb6d6c9f93233a0eaae0f7da81965 Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Mon, 9 Nov 2020 16:35:12 +0100 Subject: [PATCH] feat(select): Allow custom template for selected value --- apps/demos/src/app-routing.module.ts | 5 ++ apps/demos/src/barista-examples.a11y.ts | 1 + apps/demos/src/nav-items.ts | 4 + libs/barista-components/select/README.md | 30 ++++--- libs/barista-components/select/index.ts | 1 + .../select/src/select-module.ts | 5 +- .../select/src/select-value-template.ts | 29 +++++++ .../barista-components/select/src/select.html | 12 ++- .../select/src/select.spec.ts | 80 +++++++++++++++++++ libs/barista-components/select/src/select.ts | 6 ++ libs/examples/src/index.ts | 3 + libs/examples/src/select/index.ts | 1 + .../select-custom-value-template-example.html | 15 ++++ .../select-custom-value-template-example.ts | 34 ++++++++ .../src/select/select-examples.module.ts | 2 + 15 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 libs/barista-components/select/src/select-value-template.ts create mode 100644 libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.html create mode 100644 libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.ts diff --git a/apps/demos/src/app-routing.module.ts b/apps/demos/src/app-routing.module.ts index 0f7d523cb7..ca280d1385 100644 --- a/apps/demos/src/app-routing.module.ts +++ b/apps/demos/src/app-routing.module.ts @@ -323,6 +323,7 @@ import { DtExampleTreeTableSimple, DtExampleComboboxCustomOptionHeight, DtExampleFilterFieldInfiniteDataDepth, + DtExampleSelectCustomValueTemplate, } from '@dynatrace/examples'; // The Routing Module replaces the routing configuration in the root or feature module. @@ -1157,6 +1158,10 @@ const ROUTES: Routes = [ component: DtExampleTreeTableProblemIndicator, }, { path: 'tree-table-simple-example', component: DtExampleTreeTableSimple }, + { + path: 'select-custom-value-template-example', + component: DtExampleSelectCustomValueTemplate, + }, ]; @NgModule({ diff --git a/apps/demos/src/barista-examples.a11y.ts b/apps/demos/src/barista-examples.a11y.ts index a45dec0f61..ab4d445bb1 100644 --- a/apps/demos/src/barista-examples.a11y.ts +++ b/apps/demos/src/barista-examples.a11y.ts @@ -35,6 +35,7 @@ const BLOCKLIST: string[] = [ 'select-groups-example', 'select-value-example', 'select-with-icons-example', + 'select-custom-value-template-example', // Disabled the filter field tests, because their `combobox` role does not // fulfil all requirements of a combobox. diff --git a/apps/demos/src/nav-items.ts b/apps/demos/src/nav-items.ts index 80e71d1299..954183f11d 100644 --- a/apps/demos/src/nav-items.ts +++ b/apps/demos/src/nav-items.ts @@ -1163,6 +1163,10 @@ export const DT_DEMOS_EXAMPLE_NAV_ITEMS = [ name: 'select-with-icons-example', route: '/select-with-icons-example', }, + { + name: 'select-custom-value-template-example', + route: '/select-custom-value-template-example', + }, ], }, { diff --git a/libs/barista-components/select/README.md b/libs/barista-components/select/README.md index 6f5ca2215a..5dd42b1d77 100644 --- a/libs/barista-components/select/README.md +++ b/libs/barista-components/select/README.md @@ -41,17 +41,17 @@ in the Angular forms documentation). ## DtSelect inputs -| Name | Type | Description | -| ------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `placeholder` | `string` | Placeholder to be shown if no value has been selected. | -| `required` | `boolean` | Whether the component is required. | -| `compareWith` | `(v1: T, v2: T) => boolean` | Function to compare the option values with the selected values. The first argument is a value from an option. The second is a value from the selection. A boolean should be returned. Defaults to value equality. | -| `value` | `T` | Value of the select control. | -| `id` | `string` | Unique id of the element. | -| `aria-label` | `string` | Aria label of the select. If not specified, the placeholder will be used as label. | -| `aria-labelledby` | `string` | Input that can be used to specify the `aria-labelledby` attribute. | -| `errorStateMatcher` | `ErrorStateMatcher` | Object used to control when error messages are shown. | -| `panelClass` | `string | string[] | Set | { [key: string]: any }` | Classes to be passed to the select panel. Supports the same syntax as `ngClass`. | +| Name | Type | Description | +| ------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `placeholder` | `string` | Placeholder to be shown if no value has been selected. | +| `required` | `boolean` | Whether the component is required. | +| `compareWith` | `(v1: T, v2: T) => boolean` | Function to compare the option values with the selected values. The first argument is a value from an option. The second is a value from the selection. A boolean should be returned. Defaults to value equality. | +| `value` | `T` | Value of the select control. | +| `id` | `string` | Unique id of the element. | +| `aria-label` | `string` | Aria label of the select. If not specified, the placeholder will be used as label. | +| `aria-labelledby` | `string` | Input that can be used to specify the `aria-labelledby` attribute. | +| `errorStateMatcher` | `ErrorStateMatcher` | Object used to control when error messages are shown. | +| `panelClass` | `string | string[] | Set | { [key: string]: any }` | Classes to be passed to the select panel. Supports the same syntax as `ngClass`. | ## DtSelect outputs @@ -119,6 +119,14 @@ select. +## Custom trigger + +It is possible to customize the trigger that is displayed when the select has a +value. By using the property `` it's possible to +stablish a new template for the selected value. + + + ## Accessibility The select component without text or label should be given a meaningful label diff --git a/libs/barista-components/select/index.ts b/libs/barista-components/select/index.ts index ec2f05e6ca..929b28a430 100644 --- a/libs/barista-components/select/index.ts +++ b/libs/barista-components/select/index.ts @@ -16,3 +16,4 @@ export * from './src/select-module'; export * from './src/select'; +export * from './src/select-value-template'; diff --git a/libs/barista-components/select/src/select-module.ts b/libs/barista-components/select/src/select-module.ts index 328704fef0..0bf775a72f 100644 --- a/libs/barista-components/select/src/select-module.ts +++ b/libs/barista-components/select/src/select-module.ts @@ -23,10 +23,11 @@ import { DtFormFieldModule } from '@dynatrace/barista-components/form-field'; import { DtIconModule } from '@dynatrace/barista-components/icon'; import { DtSelect } from './select'; +import { DtSelectValueTemplate } from './select-value-template'; @NgModule({ imports: [CommonModule, OverlayModule, DtIconModule, DtOptionModule], - exports: [DtFormFieldModule, DtOptionModule, DtSelect], - declarations: [DtSelect], + exports: [DtFormFieldModule, DtOptionModule, DtSelect, DtSelectValueTemplate], + declarations: [DtSelect, DtSelectValueTemplate], }) export class DtSelectModule {} diff --git a/libs/barista-components/select/src/select-value-template.ts b/libs/barista-components/select/src/select-value-template.ts new file mode 100644 index 0000000000..1c6faf0bd8 --- /dev/null +++ b/libs/barista-components/select/src/select-value-template.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '@angular/core'; + +/** + * Allows the user to customize the trigger that is displayed when the select has a value. + */ +@Component({ + selector: 'dt-select-value-template', + template: '', + host: { + class: 'dt-select-value-template', + }, +}) +export class DtSelectValueTemplate {} diff --git a/libs/barista-components/select/src/select.html b/libs/barista-components/select/src/select.html index 4c5707dde1..e0c51a09a6 100644 --- a/libs/barista-components/select/src/select.html +++ b/libs/barista-components/select/src/select.html @@ -10,8 +10,16 @@ {{ placeholder || '\u00A0' }} - - {{ triggerValue || '\u00A0' }} + + {{ triggerValue || '\u00A0' }} + diff --git a/libs/barista-components/select/src/select.spec.ts b/libs/barista-components/select/src/select.spec.ts index 9d770b59db..1dd3461b19 100644 --- a/libs/barista-components/select/src/select.spec.ts +++ b/libs/barista-components/select/src/select.spec.ts @@ -75,6 +75,7 @@ import { createComponent, createKeyboardEvent, } from '@dynatrace/testing/browser'; +import { DtSelectValueTemplate } from './select-value-template'; describe('DtSelect', () => { let overlayContainer: OverlayContainer; @@ -119,6 +120,7 @@ describe('DtSelect', () => { SelectWithGroupsAndNgContainer, SelectWithFormFieldLabel, SelectWithOptionValueZero, + SelectWithCustomTrigger, ]); })); @@ -684,6 +686,57 @@ describe('DtSelect', () => { }); }); + describe('custom trigger', () => { + let fixture: ComponentFixture; + let trigger: HTMLElement; + + beforeEach(fakeAsync(() => { + fixture = createComponent(SelectWithCustomTrigger); + trigger = fixture.debugElement.query(By.css('.dt-select-trigger')) + .nativeElement; + })); + + it('should be undefined', fakeAsync(() => { + trigger.click(); + fixture.detectChanges(); + flush(); + + const option = overlayContainerElement.querySelector( + 'dt-option', + ) as HTMLElement; + option.click(); + fixture.detectChanges(); + flush(); + + const customTrigger = fixture.debugElement.query( + By.directive(DtSelectValueTemplate), + ); + + expect(customTrigger).toBeNull(); + })); + + it('should be defined', fakeAsync(() => { + trigger.click(); + fixture.detectChanges(); + flush(); + + fixture.componentInstance._customTemplate = true; + + const option = overlayContainerElement.querySelector( + 'dt-option', + ) as HTMLElement; + option.click(); + fixture.detectChanges(); + flush(); + + const customTrigger = fixture.debugElement.query( + By.directive(DtSelectValueTemplate), + ); + + expect(customTrigger).toBeDefined(); + })); + }); + describe('overlay panel', () => { let fixture: ComponentFixture; let trigger: HTMLElement; @@ -2495,3 +2548,30 @@ class ResetValuesSelect { class SelectWithOptionValueZero { @ViewChild(DtSelect) select: DtSelect; } + +@Component({ + template: ` + + + + + + + {{ service.viewValue }} + + None + + + `, +}) +class SelectWithCustomTrigger { + services: any[] = [ + { value: 'cloud-spanner', viewValue: 'Cloud Spanner' }, + { value: 'cloud-sql', viewValue: 'Cloud SQL' }, + { value: 'cloud-storage', viewValue: 'Cloud Storage' }, + ]; + control = new FormControl(); + _customTemplate: boolean = false; + + @ViewChild(DtSelect) select: DtSelect; +} diff --git a/libs/barista-components/select/src/select.ts b/libs/barista-components/select/src/select.ts index ddaaa6ef0b..bce8d9c65e 100644 --- a/libs/barista-components/select/src/select.ts +++ b/libs/barista-components/select/src/select.ts @@ -44,6 +44,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + ContentChild, ContentChildren, DoCheck, ElementRef, @@ -103,6 +104,7 @@ import { DtFormField, DtFormFieldControl, } from '@dynatrace/barista-components/form-field'; +import { DtSelectValueTemplate } from './select-value-template'; let uniqueId = 0; @@ -438,6 +440,10 @@ export class DtSelect @ViewChild(CdkConnectedOverlay) overlayDir: CdkConnectedOverlay; + /** @internal Custom trigger template. */ + @ContentChild(DtSelectValueTemplate) + _customValueTemplate: DtSelectValueTemplate; + /** All of the defined select options. */ @ContentChildren(DtOption, { descendants: true }) options: QueryList< DtOption diff --git a/libs/examples/src/index.ts b/libs/examples/src/index.ts index 9b7f134488..c134e67ef2 100644 --- a/libs/examples/src/index.ts +++ b/libs/examples/src/index.ts @@ -247,6 +247,7 @@ import { DtExampleSecondaryNavMulti } from './secondary-nav/secondary-nav-multi- import { DtExampleSecondaryNavRouterLinkActive } from './secondary-nav/secondary-nav-router-link-active-example/secondary-nav-router-link-active-example'; import { DtExampleSecondaryNavTitle } from './secondary-nav/secondary-nav-title-example/secondary-nav-title-example'; import { DtExampleSelectComplexValue } from './select/select-complex-value-example/select-complex-value-example'; +import { DtExampleSelectCustomValueTemplate } from './select/select-custom-value-template-example/select-custom-value-template-example'; import { DtExampleSelectDefault } from './select/select-default-example/select-default-example'; import { DtExampleSelectDisabled } from './select/select-disabled-example/select-disabled-example'; import { DtExampleSelectFormField } from './select/select-form-field-example/select-form-field-example'; @@ -704,6 +705,7 @@ export { DtExampleTreeTableProblemIndicator, DtExampleTreeTableSimple, DtExampleFilterFieldInfiniteDataDepth, + DtExampleSelectCustomValueTemplate, }; export const EXAMPLES_MAP = new Map>([ @@ -989,6 +991,7 @@ export const EXAMPLES_MAP = new Map>([ ['DtExampleSelectGroups', DtExampleSelectGroups], ['DtExampleSelectValue', DtExampleSelectValue], ['DtExampleSelectWithIcons', DtExampleSelectWithIcons], + ['DtExampleSelectCustomValueTemplate', DtExampleSelectCustomValueTemplate], ['DtExampleShowMoreDark', DtExampleShowMoreDark], ['DtExampleShowMoreDefault', DtExampleShowMoreDefault], ['DtExampleShowMoreDisabled', DtExampleShowMoreDisabled], diff --git a/libs/examples/src/select/index.ts b/libs/examples/src/select/index.ts index 4e194bf1f7..cc043a0882 100644 --- a/libs/examples/src/select/index.ts +++ b/libs/examples/src/select/index.ts @@ -23,3 +23,4 @@ export * from './select-forms-example/select-forms-example'; export * from './select-groups-example/select-groups-example'; export * from './select-value-example/select-value-example'; export * from './select-with-icons-example/select-with-icons-example'; +export * from './select-custom-value-template-example/select-custom-value-template-example'; diff --git a/libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.html b/libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.html new file mode 100644 index 0000000000..ea5f186f90 --- /dev/null +++ b/libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.html @@ -0,0 +1,15 @@ + + + + + +
+ + {{ color.viewValue }} +
+
+
diff --git a/libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.ts b/libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.ts new file mode 100644 index 0000000000..54c6334058 --- /dev/null +++ b/libs/examples/src/select/select-custom-value-template-example/select-custom-value-template-example.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'dt-example-select-custom-value-template-example', + templateUrl: './select-custom-value-template-example.html', + styles: [ + 'dt-option div, ::ng-deep .dt-select-trigger .dt-select-value { display: flex; align-items: center; }', + '.dt-select-value-template span, dt-option span { display: flex; height: 15px; width: 15px; margin-right: 5px; border-radius: 2px; }', + ], +}) +export class DtExampleSelectCustomValueTemplate { + selectedValue: { value: string; viewValue: string }; + colors = [ + { viewValue: 'Red', value: 'red' }, + { viewValue: 'Blue', value: 'blue' }, + { viewValue: 'Green', value: 'green' }, + ]; +} diff --git a/libs/examples/src/select/select-examples.module.ts b/libs/examples/src/select/select-examples.module.ts index 969e92fd5a..3a7698096a 100644 --- a/libs/examples/src/select/select-examples.module.ts +++ b/libs/examples/src/select/select-examples.module.ts @@ -22,6 +22,7 @@ import { DtIconModule } from '@dynatrace/barista-components/icon'; import { DtFormFieldModule } from '@dynatrace/barista-components/form-field'; import { DtCheckboxModule } from '@dynatrace/barista-components/checkbox'; import { DtExampleSelectComplexValue } from './select-complex-value-example/select-complex-value-example'; +import { DtExampleSelectCustomValueTemplate } from './select-custom-value-template-example/select-custom-value-template-example'; import { DtExampleSelectDefault } from './select-default-example/select-default-example'; import { DtExampleSelectDisabled } from './select-disabled-example/select-disabled-example'; import { DtExampleSelectFormField } from './select-form-field-example/select-form-field-example'; @@ -49,6 +50,7 @@ import { DtExampleSelectValue } from './select-value-example/select-value-exampl DtExampleSelectGroups, DtExampleSelectValue, DtExampleSelectWithIcons, + DtExampleSelectCustomValueTemplate, ], }) export class DtExamplesSelectModule {}