diff --git a/projects/common-form-elements/src/lib/common-form-config.ts b/projects/common-form-elements/src/lib/common-form-config.ts index 5ff9311def7050dc734cf20a4686f9a1302b712d..e3248501f605dbbd24866ce0c42792f5028d3b60 100644 --- a/projects/common-form-elements/src/lib/common-form-config.ts +++ b/projects/common-form-elements/src/lib/common-form-config.ts @@ -67,3 +67,7 @@ export interface FieldConfig<T> { }[]; asyncValidation?: FieldConfigAsyncValidation; } + +export enum FilterType { + FACET = 'facet' +} diff --git a/projects/common-form-elements/src/lib/common-form-elements.module.ts b/projects/common-form-elements/src/lib/common-form-elements.module.ts index 098ee971de85c6f62e22d8ad98e6dcd1aa288170..f25604d1a3c9100fa7d6111d75f59d3385d180b8 100644 --- a/projects/common-form-elements/src/lib/common-form-elements.module.ts +++ b/projects/common-form-elements/src/lib/common-form-elements.module.ts @@ -15,6 +15,8 @@ import { PipesModule } from './pipes/pipes.module'; import { RedExclamationComponent } from './icon/red-exclamation/red-exclamation.component'; import { GreenTickComponent } from './icon/green-tick/green-tick.component'; import { EmptyCircleComponent } from './icon/empty-circle/empty-circle.component'; +import { FiltersComponent } from './filters/filters.component'; +import { PillsComponent } from './pills/pills.component'; @NgModule({ declarations: [ @@ -30,7 +32,9 @@ import { EmptyCircleComponent } from './icon/empty-circle/empty-circle.component CaretDownComponent, RedExclamationComponent, GreenTickComponent, - EmptyCircleComponent + EmptyCircleComponent, + FiltersComponent, + PillsComponent ], imports: [ CommonModule, @@ -51,7 +55,8 @@ import { EmptyCircleComponent } from './icon/empty-circle/empty-circle.component RedExclamationComponent, GreenTickComponent, EmptyCircleComponent, - MultipleDropdownComponent + MultipleDropdownComponent, + FiltersComponent ] }) export class CommonFormElementsModule { } diff --git a/projects/common-form-elements/src/lib/dropdown/dropdown.component.html b/projects/common-form-elements/src/lib/dropdown/dropdown.component.html index f5ba984d7c23ebb1d8f2e65482903cbe8859f2dd..9fee3cca69743176df2672ca506f14101b779188 100644 --- a/projects/common-form-elements/src/lib/dropdown/dropdown.component.html +++ b/projects/common-form-elements/src/lib/dropdown/dropdown.component.html @@ -1,4 +1,4 @@ -<div class="sb-dropdown"> +<div class="sb-dropdown" *ngIf="!type"> <label>{{label}}</label> <ng-container *ngIf="options"> <div class="dropdown-container"> @@ -24,3 +24,21 @@ </div> </ng-container> </div> + +<!-- Dropdown for filters component --> +<div class="sb-dropdown" *ngIf="type === 'facet'"> + <label>{{label}}</label> + <ng-container *ngIf="options && options.data"> + <div class="dropdown-container"> + <sb-icon-dropdown class="dropdown-icon"></sb-icon-dropdown> + <select [attr.disabled]="disabled ? true : ( context ? (context.invalid ? true : null) : null )" + class="sb-dropdown-select" [ngClass]="(styleClass ? styleClass : 'default-dropdown')" id="sb-dropdown" name="sb-dropdown" + (change)="onChangeFacet($event)" placeholder="placeHolder"> + <option *ngIf="!default" [disabled]="true" selected>{{placeHolder}}</option> + <option *ngFor="let option of options.data" [ngValue]="option" [selected]="options.default === option.value">{{option.label}}</option> + </select> + </div> + </ng-container> +</div> + +<!-- Dropdown for filters component --> diff --git a/projects/common-form-elements/src/lib/dropdown/dropdown.component.ts b/projects/common-form-elements/src/lib/dropdown/dropdown.component.ts index b84ada3fb7878ecd441fbb85b7f4e9140c379810..b55e435a35ac906fcffcd96f9ada6c2d6618b291 100644 --- a/projects/common-form-elements/src/lib/dropdown/dropdown.component.ts +++ b/projects/common-form-elements/src/lib/dropdown/dropdown.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core'; +import {Component, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, EventEmitter} from '@angular/core'; import {FormControl} from '@angular/forms'; import {Observable, Subject, Subscription} from 'rxjs'; import {FieldConfigOption, FieldConfigOptionsBuilder} from '../common-form-config'; @@ -23,10 +23,12 @@ export class DropdownComponent implements OnInit, OnChanges, OnDestroy { @Input() default?: any; @Input() contextData: any; @Input() dataLoadStatusDelegate: Subject<'LOADING' | 'LOADED'>; - + @Input() type?: string; + @Input() styleClass?: string; + @Output() onChangeFilter: EventEmitter<any> = new EventEmitter(); options$?: Observable<FieldConfigOption<any>[]>; contextValueChangesSubscription?: Subscription; - + selectedType: any; constructor() { } @@ -70,4 +72,13 @@ export class DropdownComponent implements OnInit, OnChanges, OnDestroy { isOptionsMap(input: any) { return !Array.isArray(input) && typeof input === 'object'; } + + onChangeFacet($event) { + const selectedObject = this.options.data[$event.currentTarget.options.selectedIndex - 1]; + let emitPayload = JSON.parse(JSON.stringify(this.options)); + emitPayload['data'] = selectedObject; + emitPayload['selectedLabel'] = selectedObject.label; + emitPayload['selectedValue'] = selectedObject.value; + this.onChangeFilter.emit(emitPayload); + } } diff --git a/projects/common-form-elements/src/lib/filters/filters.component.css b/projects/common-form-elements/src/lib/filters/filters.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/projects/common-form-elements/src/lib/filters/filters.component.html b/projects/common-form-elements/src/lib/filters/filters.component.html new file mode 100644 index 0000000000000000000000000000000000000000..c43ada71aec972aba18072894a3c520b67e953a0 --- /dev/null +++ b/projects/common-form-elements/src/lib/filters/filters.component.html @@ -0,0 +1,19 @@ +<div *ngIf="type === FilterType.FACET"> + <div *ngFor="let facet of formattedFacets"> + + <!-- Start of Dropdown --> + <sb-dropdown *ngIf="facet.type === 'dropdown'" [type]="type" [label]="facet.label" [options]="facet" + [placeHolder]="facet.placeHolder" [default]="facet.default" [styleClass]="styleClass" + (onChangeFilter)="selectedFilter.emit($event)"> + </sb-dropdown> + <!-- End of Dropdown --> + + <!-- Start of pills --> + <div *ngIf="facet.type === 'pills'"> + <sb-pills [label]="facet.label" [options]="facet" [styleClass]="styleClass" + (onChangeFilter)="selectedFilter.emit($event)"></sb-pills> + </div> + <!-- End of pills --> + + </div> +</div> \ No newline at end of file diff --git a/projects/common-form-elements/src/lib/filters/filters.component.spec.ts b/projects/common-form-elements/src/lib/filters/filters.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ee973d307ceff06b4f931f6aa3dcbd6ef1053e5 --- /dev/null +++ b/projects/common-form-elements/src/lib/filters/filters.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FiltersComponent } from './filters.component'; + +describe('FiltersComponent', () => { + let component: FiltersComponent; + let fixture: ComponentFixture<FiltersComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FiltersComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FiltersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/common-form-elements/src/lib/filters/filters.component.ts b/projects/common-form-elements/src/lib/filters/filters.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..30ac7f5bb4cd38bbccc441a6706802ea29fdf8fa --- /dev/null +++ b/projects/common-form-elements/src/lib/filters/filters.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; + +import { FilterType } from '../common-form-config'; + +@Component({ + selector: 'sb-filters', + templateUrl: './filters.component.html', + styleUrls: ['./filters.component.css'] +}) +export class FiltersComponent implements OnInit { + + @Input() type: string; + @Input() facets: any = []; + @Input() config?: object; + @Input() styleClass?: string; + @Output() selectedFilter: EventEmitter<any> = new EventEmitter(); + FilterType = FilterType; + formGroup: FormGroup; + + formattedFacets: any = []; + + constructor() { + } + + ngOnInit() { + this.formatFacets(); + } + + formatFacets () { + this.facets.forEach((facet, index) => { + let facetObj = {}; + facetObj['name'] = facet['name']; + facetObj['type'] = this.config[facet['name']] && this.config[facet['name']]['type'] ? this.config[facet['name']]['type'] : 'dropdown'; + facetObj['index'] = this.config[facet['name']] && this.config[facet['name']]['index'] ? this.config[facet['name']]['index'] : index; + facetObj['label'] = this.config[facet['name']] && this.config[facet['name']]['label'] ? this.config[facet['name']]['label'] : facet['name']; + facetObj['placeHolder'] = this.config[facet['name']] && this.config[facet['name']]['placeHolder'] ? this.config[facet['name']]['placeHolder'] : 'Select ' + facet['name']; + + facetObj['default'] = this.config[facet['name']] && this.config[facet['name']]['default'] ? this.config[facet['name']]['default'] : ''; + + facetObj['data'] = []; + facet['values'].forEach(facetValue => { + facetObj['data'].push({ + label: facetValue['name'], + value: facetValue['name'] + }); + }); + this.formattedFacets.push(facetObj); + this.formattedFacets.sort((a, b) => a.index - b.index); + }); + } +} diff --git a/projects/common-form-elements/src/lib/pills/pills.component.css b/projects/common-form-elements/src/lib/pills/pills.component.css new file mode 100644 index 0000000000000000000000000000000000000000..fa809e360b871d0a1dae60e62bb371ac6b6390e4 --- /dev/null +++ b/projects/common-form-elements/src/lib/pills/pills.component.css @@ -0,0 +1,113 @@ +.sbt-class-bar .sb-slider-pills-container { + overflow-x: inherit !important; +} +.sbt-class-bar .sb-slider-pills-container .sb-grade-pills-container { + background: inherit !important; + padding-top: 0px !important; +} +.sbt-class-bar .sb-slider-pills-container .sb-pills-container { + display: flex !important; + flex-wrap: wrap; + justify-content: flex-start; + margin: 0px -8px 0px 0; +} +html[dir="rtl"] .sbt-class-bar .sb-slider-pills-container .sb-pills-container { + margin: 0px 0 0px -8px !important; +} +.sbt-class-bar .sb-slider-pills-container .sb-pills-container .pill { + padding: 8px 16px !important; + color: #ffffff !important; + margin: 0 8px 8px 0 !important; + background-color: #0077FF; + font-size: 12px !important; + display: unset !important; + width: 100px; + flex-basis: 100px; + flex-grow: 1; +} +html[dir="rtl"] .sbt-class-bar .sb-slider-pills-container .sb-pills-container .pill { + margin: 0 0 8px 8px !important; +} +.sbt-class-bar .sb-slider-pills-container .sb-pills-container .pill.active, .sbt-class-bar .sb-slider-pills-container .sb-pills-container .pill:hover { + color: #ffffff !important; + background-color: #0076FE !important; + box-shadow: rgba(0, 0, 0, 0.1) 3px 3px 2px 0px; +} + +.sb-slider-pills-container { + overflow-x: auto; +} +.sb-slider-pills-container .sb-pills-container { + display: inline-flex; + align-items: center; + box-sizing: border-box; +} +.sb-slider-pills-container .sb-pills-container .pill { + background-color: #ffffff; + white-space: nowrap; + -webkit-appearance: none; + text-overflow: ellipsis; + min-height: 32px; + cursor: pointer; + transition: all 0.25s ease-in-out; + border-radius: 2px; + text-transform: capitalize; + text-decoration: none; + text-align: center; + font-weight: normal; + font-style: normal; + font-stretch: normal; + font-size: 12px; + box-sizing: border-box; + display: flex; + align-items: center; + line-height: 16px; +} +.sb-slider-pills-container .sb-medium-pills-container { + min-height: 64px; + background: #ffffff; + min-width: 100%; +} +.sb-slider-pills-container .sb-medium-pills-container .pill { + border: #008840 1px solid; + color: #008840; +} +.sb-slider-pills-container .sb-medium-pills-container .pill:active, .sb-slider-pills-container .sb-medium-pills-container .pill:hover, .sb-slider-pills-container .sb-medium-pills-container .pill.active { + color: #ffffff; + background-color: #008840; + box-shadow: 0 3px 4px 0 rgba(0, 0, 0, ); +} +.sb-slider-pills-container .sb-grade-pills-container { + min-height: 48px; + /* background: var(--gray-0); */ + min-width: 100%; +} +.sb-slider-pills-container .sb-grade-pills-container .pill { + border-radius: 16px; + color: #024f9d; + min-height: 32px; +} +.sb-slider-pills-container .sb-grade-pills-container .pill:active, .sb-slider-pills-container .sb-grade-pills-container .pill:hover, .sb-slider-pills-container .sb-grade-pills-container .pill.active { + background-color: #024f9d; + color: #ffffff; + box-shadow: 0 3px 4px 0 rgba(0, 0, 0, ); +} +.sb-slider-pills-container .sb-grade-pills-container .pill:active .icon-svg svg, .sb-slider-pills-container .sb-grade-pills-container .pill:hover .icon-svg svg, .sb-slider-pills-container .sb-grade-pills-container .pill.active .icon-svg svg { + fill: #ffffff; +} +.sb-slider-pills-container .sb-grade-pills-container .pill.rounded { + font-size: 14px; +} +.sb-slider-pills-container .sb-grade-pills-container .pill.rounded-with-icon .icon-svg { + margin-right: 4px; + top: 0; +} +html[dir="rtl"] .sb-slider-pills-container .sb-grade-pills-container .pill.rounded-with-icon .icon-svg { + margin-left: 4px; + margin-right: inherit; +} +.sb-slider-pills-container .sb-grade-pills-container .pill.rounded-with-icon .name { + align-self: center; + display: inline-block; + line-height: 16px; +} diff --git a/projects/common-form-elements/src/lib/pills/pills.component.html b/projects/common-form-elements/src/lib/pills/pills.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e4f92fb2f41b63a93a4ca7614f2420f2c6a83493 --- /dev/null +++ b/projects/common-form-elements/src/lib/pills/pills.component.html @@ -0,0 +1,11 @@ +<div class="sbt-class-bar"> + <label>{{label}}</label> + <div class="sb-slider-pills-container"> + <div class="sb-pills-container sb-grade-pills-container" id="gradeScroll"> + <div class="pill rounded" *ngFor="let facet of options.data; let i = index;" [ngClass]="((options.default === facet.value ? 'active' : '') + ' ' + styleClass)" + attr.id="class{{i}}" (click)="onChangeFacet(facet)"> + {{facet.value}} + </div> + </div> + </div> +</div> diff --git a/projects/common-form-elements/src/lib/pills/pills.component.spec.ts b/projects/common-form-elements/src/lib/pills/pills.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..acea49f2edbc27c36494a600c9ad08a64ddde23a --- /dev/null +++ b/projects/common-form-elements/src/lib/pills/pills.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PillsComponent } from './pills.component'; + +describe('PillsComponent', () => { + let component: PillsComponent; + let fixture: ComponentFixture<PillsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PillsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PillsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/common-form-elements/src/lib/pills/pills.component.ts b/projects/common-form-elements/src/lib/pills/pills.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..68441d1227e7433496d813fbfbdc0622eabdf5ea --- /dev/null +++ b/projects/common-form-elements/src/lib/pills/pills.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'sb-pills', + templateUrl: './pills.component.html', + styleUrls: ['./pills.component.css'] +}) +export class PillsComponent implements OnInit { + @Input() options: any = []; + @Input() label?: string; + @Input() styleClass?: string; + @Output() onChangeFilter: EventEmitter<any> = new EventEmitter(); + + constructor() { } + + ngOnInit() { + } + + onChangeFacet(selectedValue) { + let emitPayload = JSON.parse(JSON.stringify(this.options)); + emitPayload['data'] = selectedValue; + emitPayload['selectedLabel'] = selectedValue.label; + emitPayload['selectedValue'] = selectedValue.value; + this.onChangeFilter.emit(emitPayload); + } +}