diff --git a/src/app/app.component.html b/src/app/app.component.html
index 80a3f6a..779fa92 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,3 +1,17 @@
+
+
+ My App
+
+
+
+
+
@@ -23,4 +37,15 @@
Comment Value: {{parseComment(form)}}
+
+ Choose an option
+
+
+ @for (item of filteredOptions; track $index) {
+ {{item}}
+ }
+
+
+
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 7289a64..b65713e 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -3,12 +3,21 @@ import { RouterOutlet } from '@angular/router';
import { CommentBoxComponent } from "./comment-box/comment-box.component";
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
-import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ProgressStepperComponent } from "./progress-stepper/progress-stepper.component";
+import { Observable, startWith, map } from 'rxjs';
+import { MatOptionModule } from '@angular/material/core';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { MatChipsModule } from '@angular/material/chips';
+import { AsyncPipe } from '@angular/common';
+import { MatSelectModule } from '@angular/material/select';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatIconModule } from '@angular/material/icon';
+import { SelectFilterComponent } from "./select-filter/select-filter.component";
@Component({
selector: 'app-root',
- imports: [RouterOutlet, CommentBoxComponent, MatFormFieldModule, MatInputModule, ReactiveFormsModule, ProgressStepperComponent],
+ imports: [RouterOutlet, MatToolbarModule, MatIconModule, CommentBoxComponent, MatFormFieldModule, MatInputModule, ReactiveFormsModule, ProgressStepperComponent, MatOptionModule, MatSelectModule, MatAutocompleteModule, MatChipsModule, AsyncPipe, FormsModule, SelectFilterComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
@@ -24,4 +33,16 @@ export class AppComponent {
if (comment.value)
return comment.value
}
+
+ allOptions = ['Apple', 'Banana', 'Orange', 'Pear', 'Grape'];
+ filteredOptions = this.allOptions.slice();
+ selectedItemsList = new FormControl();
+
+ selectAll(): void {
+ this.selectedItemsList.setValue(this.allOptions);
+ }
+
+ unselectAll(): void {
+ this.selectedItemsList.setValue([]);
+ }
}
diff --git a/src/app/select-filter/select-filter.component.html b/src/app/select-filter/select-filter.component.html
new file mode 100644
index 0000000..a20eb06
--- /dev/null
+++ b/src/app/select-filter/select-filter.component.html
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/src/app/select-filter/select-filter.component.scss b/src/app/select-filter/select-filter.component.scss
new file mode 100644
index 0000000..337b9de
--- /dev/null
+++ b/src/app/select-filter/select-filter.component.scss
@@ -0,0 +1,69 @@
+.mat-filter {
+ position: sticky;
+ top: -8px;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ border-bottom-color: grey;
+ z-index: 100;
+ font-size: inherit;
+ box-shadow: none;
+ border-radius: 0;
+ padding: 8px;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+
+ background-color: var(--mat-sys-surface-container);
+}
+
+.mat-filter-input {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ outline: none;
+ border: 0;
+ background-color: unset;
+ color: gray;
+ width: 100%;
+}
+
+.spinner {
+ position: absolute;
+ right: 16px;
+ top: calc(50% - 8px);
+}
+
+.noResultsMessage {
+ margin-top: 10px;
+ font-family: Roboto, "Helvetica Neue", sans-serif;
+ font-size: 16px;
+}
+
+.selection-buttons {
+ display: grid;
+ grid-template-columns: 50% 50%;
+ justify-content: center;
+ font-size: 0.8rem;
+
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ border-bottom-color: grey;
+
+ .button {
+ display: flex;
+ padding: 0.15rem;
+ justify-content: center;
+
+ &:hover {
+ cursor: pointer;
+
+ background-color: var(
+ --mat-option-hover-state-layer-color,
+ color-mix(
+ in srgb,
+ var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%),
+ transparent
+ )
+ );
+ }
+ }
+}
diff --git a/src/app/select-filter/select-filter.component.spec.ts b/src/app/select-filter/select-filter.component.spec.ts
new file mode 100644
index 0000000..eb46298
--- /dev/null
+++ b/src/app/select-filter/select-filter.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SelectFilterComponent } from './select-filter.component';
+
+describe('SelectFilterComponent', () => {
+ let component: SelectFilterComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SelectFilterComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(SelectFilterComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/select-filter/select-filter.component.ts b/src/app/select-filter/select-filter.component.ts
new file mode 100644
index 0000000..c12dc0f
--- /dev/null
+++ b/src/app/select-filter/select-filter.component.ts
@@ -0,0 +1,85 @@
+import { A, Z, ZERO, NINE, SPACE } from '@angular/cdk/keycodes';
+import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
+import { FormGroup, FormBuilder, ReactiveFormsModule } from '@angular/forms';
+import { Subscription } from 'rxjs';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatRippleModule } from '@angular/material/core';
+
+@Component({
+ selector: 'app-select-filter',
+ imports: [ReactiveFormsModule, MatProgressSpinnerModule, MatRippleModule],
+ templateUrl: './select-filter.component.html',
+ styleUrl: './select-filter.component.scss'
+})
+export class SelectFilterComponent {
+
+ private searchFormValueChangesSubscription!: Subscription;
+ @ViewChild('input', { static: true }) input!: ElementRef;
+
+ @Input('array') array: any[] = [];
+ @Input('showSpinner') showSpinner = true;
+ @Input('noResultsMessage') noResultsMessage = 'No results';
+
+ noResults = false;
+
+ localSpinner = false;
+ @Output() filteredReturn = new EventEmitter();
+ @Output() selectAll = new EventEmitter();
+ @Output() unselectAll = new EventEmitter();
+
+ public filteredItems: any = [];
+ public searchForm: FormGroup;
+
+ constructor(fb: FormBuilder) {
+ this.searchForm = fb.group({
+ value: ''
+ });
+ }
+
+ ngOnInit() {
+ this.searchFormValueChangesSubscription = this.searchForm.valueChanges.subscribe(change => {
+ if (this.showSpinner) {
+ this.localSpinner = true;
+ }
+ if (change.value) {
+ // IF THE DISPLAY MEMBER INPUT IS SET WE CHECK THE SPECIFIC PROPERTY
+
+ this.filteredItems = this.array.filter((name: string) => name.toLowerCase().includes(change.value.toLowerCase()));
+ // OTHERWISE, WE CHECK THE ENTIRE STRING
+
+ // NO RESULTS VALIDATION
+
+ this.noResults = this.filteredItems == null || this.filteredItems.length === 0;
+
+
+ } else {
+ this.filteredItems = this.array.slice();
+ this.noResults = false;
+ }
+ this.filteredReturn.emit(this.filteredItems);
+ setTimeout(() => {
+ if (this.showSpinner) {
+ this.localSpinner = false;
+ }
+ }, 2000);
+ });
+
+ setTimeout(() => {
+ this.input.nativeElement.focus();
+ }, 500);
+ }
+
+ handleKeydown(event: KeyboardEvent) {
+ // PREVENT PROPAGATION FOR ALL ALPHANUMERIC CHARACTERS IN ORDER TO AVOID SELECTION ISSUES
+ if ((event.key && event.key.length === 1) ||
+ (event.keyCode >= A && event.keyCode <= Z) ||
+ (event.keyCode >= ZERO && event.keyCode <= NINE) ||
+ (event.keyCode === SPACE)) {
+ event.stopPropagation();
+ }
+ }
+ ngOnDestroy() {
+ this.filteredReturn.emit(this.array);
+ this.searchFormValueChangesSubscription.unsubscribe();
+ }
+}