Skip to content

Commit dbb88c5

Browse files
authored
feat: support control options in select (#578)
* feat: support control options in select
1 parent 9a0b4ef commit dbb88c5

File tree

4 files changed

+98
-8
lines changed

4 files changed

+98
-8
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2+
import { SelectOptionComponent } from './select-option.component';
3+
4+
@Component({
5+
selector: 'ht-select-control-option',
6+
changeDetection: ChangeDetectionStrategy.OnPush,
7+
template: '' // No template, just gathering data
8+
})
9+
export class SelectControlOptionComponent<V> extends SelectOptionComponent<V> {
10+
@Input()
11+
public position: SelectControlOptionPosition = SelectControlOptionPosition.Top;
12+
}
13+
14+
export enum SelectControlOptionPosition {
15+
// Can be extended for bottom later.
16+
Top = 'top'
17+
}

projects/components/src/select/select.component.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { HttpClientTestingModule } from '@angular/common/http/testing';
22
import { fakeAsync, flush } from '@angular/core/testing';
3-
import { IconLibraryTestingModule } from '@hypertrace/assets-library';
3+
import { IconLibraryTestingModule, IconType } from '@hypertrace/assets-library';
44
import { NavigationService } from '@hypertrace/common';
55
import { createHostFactory, mockProvider, SpectatorHost } from '@ngneat/spectator/jest';
66
import { EMPTY } from 'rxjs';
7+
import { SelectControlOptionPosition } from './select-control-option.component';
78
import { SelectJustify } from './select-justify';
89
import { SelectComponent, SelectTriggerDisplayMode } from './select.component';
910
import { SelectModule } from './select.module';
@@ -198,4 +199,38 @@ describe('Select Component', () => {
198199
expect(spectator.element).toHaveText(selectionOptions[1].label);
199200
expect(spectator.query('.trigger-content')).toBe(spectator.query('.justify-center'));
200201
}));
202+
203+
test('should show control options on the top as expected', fakeAsync(() => {
204+
const onChange = jest.fn();
205+
206+
spectator = hostFactory(
207+
`
208+
<ht-select (selectedChange)="onChange($event)">
209+
<ht-select-option *ngFor="let option of options" [label]="option.label" [value]="option.value"></ht-select-option>
210+
<ht-select-control-option [label]="controlLabel" [value]="controlValue" [position]="controlPosition" [icon]="controlIcon"></ht-select-control-option>
211+
</ht-select>`,
212+
{
213+
hostProps: {
214+
options: selectionOptions,
215+
controlLabel: 'None',
216+
controlValue: 'none-id',
217+
controlIcon: IconType.Debug,
218+
controlPosition: SelectControlOptionPosition.Top,
219+
onChange: onChange
220+
}
221+
}
222+
);
223+
224+
spectator.tick();
225+
spectator.click('.trigger-content');
226+
227+
const optionElements = spectator.queryAll('.select-option', { root: true });
228+
expect(optionElements[0].querySelector('.icon')).toExist();
229+
expect(optionElements[0]).toContainText('None');
230+
spectator.click(optionElements[0]);
231+
232+
expect(onChange).toHaveBeenCalledTimes(1);
233+
expect(onChange).toHaveBeenCalledWith('none-id');
234+
flush();
235+
}));
201236
});

projects/components/src/select/select.component.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { LoggerService, queryListAndChanges$, SubscriptionLifecycle, TypedSimple
1515
import { EMPTY, merge, Observable, of } from 'rxjs';
1616
import { map, switchMap } from 'rxjs/operators';
1717
import { IconSize } from '../icon/icon-size';
18+
import { SelectControlOptionComponent, SelectControlOptionPosition } from './select-control-option.component';
1819
import { SelectGroupPosition } from './select-group-position';
1920
import { SelectJustify } from './select-justify';
2021
import { SelectOption } from './select-option';
@@ -74,22 +75,38 @@ import { SelectSize } from './select-size';
7475
</ht-popover-trigger>
7576
<ht-popover-content>
7677
<div class="select-content" [ngStyle]="{ 'minWidth.px': triggerContainer.offsetWidth }">
78+
<ng-container *htLetAsync="this.topControlItems$ as topControlItems">
79+
<div *ngIf="topControlItems?.length !== 0">
80+
<ng-container
81+
*ngTemplateOutlet="itemsTemplate; context: { items: topControlItems, showSelectionStatus: false }"
82+
></ng-container>
83+
84+
<ht-divider></ht-divider>
85+
</div>
86+
</ng-container>
87+
88+
<ng-container
89+
*ngTemplateOutlet="itemsTemplate; context: { items: items, showSelectionStatus: true }"
90+
></ng-container>
91+
</div>
92+
93+
<ng-template #itemsTemplate let-items="items" let-showSelectionStatus="showSelectionStatus">
7794
<div
7895
*ngFor="let item of items"
7996
(click)="this.onSelectionChange(item)"
8097
class="select-option"
8198
[ngClass]="this.size"
8299
>
100+
<ht-icon *ngIf="item.icon" class="icon" [icon]="item.icon" size="${IconSize.Small}"> </ht-icon>
83101
<span class="label">{{ item.label }}</span>
84102
<ht-icon
85103
class="status-icon"
86-
*ngIf="this.highlightSelected && this.isSelectedItem(item)"
104+
*ngIf="showSelectionStatus && this.highlightSelected && this.isSelectedItem(item)"
87105
icon="${IconType.Checkmark}"
88106
size="${IconSize.Small}"
89-
>
90-
</ht-icon>
107+
></ht-icon>
91108
</div>
92-
</div>
109+
</ng-template>
93110
</ht-popover-content>
94111
</ht-popover>
95112
</div>
@@ -132,10 +149,15 @@ export class SelectComponent<V> implements AfterContentInit, OnChanges {
132149
@ContentChildren(SelectOptionComponent)
133150
public items?: QueryList<SelectOptionComponent<V>>;
134151

152+
@ContentChildren(SelectControlOptionComponent)
153+
public controlItems?: QueryList<SelectControlOptionComponent<V>>;
154+
135155
public selected$?: Observable<SelectOption<V> | undefined>;
136156

137157
public groupPosition: SelectGroupPosition = SelectGroupPosition.Ungrouped;
138158

159+
public topControlItems$?: Observable<SelectControlOptionComponent<V>[]>;
160+
139161
public get justifyClass(): string {
140162
if (this.justify !== undefined) {
141163
return this.justify;
@@ -151,6 +173,11 @@ export class SelectComponent<V> implements AfterContentInit, OnChanges {
151173

152174
public ngAfterContentInit(): void {
153175
this.selected$ = this.buildObservableOfSelected();
176+
if (this.controlItems !== undefined) {
177+
this.topControlItems$ = queryListAndChanges$(this.controlItems).pipe(
178+
map(items => items.filter(item => item.position === SelectControlOptionPosition.Top))
179+
);
180+
}
154181
}
155182

156183
public ngOnChanges(changes: TypedSimpleChanges<this>): void {
Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
import { CommonModule } from '@angular/common';
22
import { NgModule } from '@angular/core';
33
import { FormsModule } from '@angular/forms';
4+
import { DividerModule } from '../divider/divider.module';
45
import { IconModule } from '../icon/icon.module';
56
import { LabelModule } from '../label/label.module';
67
import { LetAsyncModule } from '../let-async/let-async.module';
78
import { PopoverModule } from '../popover/popover.module';
89
import { TooltipModule } from '../tooltip/tooltip.module';
10+
import { SelectControlOptionComponent } from './select-control-option.component';
911
import { SelectGroupComponent } from './select-group.component';
1012
import { SelectOptionComponent } from './select-option.component';
1113
import { SelectComponent } from './select.component';
1214

1315
@NgModule({
14-
imports: [FormsModule, CommonModule, IconModule, LabelModule, LetAsyncModule, PopoverModule, TooltipModule],
15-
declarations: [SelectComponent, SelectOptionComponent, SelectGroupComponent],
16-
exports: [SelectComponent, SelectOptionComponent, SelectGroupComponent]
16+
imports: [
17+
FormsModule,
18+
CommonModule,
19+
IconModule,
20+
LabelModule,
21+
LetAsyncModule,
22+
PopoverModule,
23+
TooltipModule,
24+
DividerModule
25+
],
26+
declarations: [SelectComponent, SelectOptionComponent, SelectGroupComponent, SelectControlOptionComponent],
27+
exports: [SelectComponent, SelectOptionComponent, SelectGroupComponent, SelectControlOptionComponent]
1728
})
1829
export class SelectModule {}

0 commit comments

Comments
 (0)