diff --git a/projects/go-lib/src/lib/animations/search.animation.ts b/projects/go-lib/src/lib/animations/search.animation.ts
new file mode 100644
index 000000000..778306c11
--- /dev/null
+++ b/projects/go-lib/src/lib/animations/search.animation.ts
@@ -0,0 +1,57 @@
+import {
+ animate,
+ state,
+ style,
+ transition,
+ trigger
+} from '@angular/animations';
+
+const timing = '.5s'
+const easing = 'cubic-bezier(.25, .8, .25, 1)';
+
+export const searchLoaderAnim = trigger('searchLoaderAnim', [
+ transition(':enter', [
+ style({
+ height: 0,
+ opacity: 0,
+ padding: 0
+ }),
+ animate(timing + ' ' + easing, style({
+ height: '*',
+ opacity: 1,
+ padding: '2rem'
+ }))
+ ]),
+ transition(':leave', [
+ animate(timing + ' ' + easing, style({
+ height: 0,
+ opacity: 0,
+ padding: 0
+ }))
+ ])
+])
+
+export const searchResultsAnim = trigger('searchResultsAnim', [
+ transition(':enter', [
+ style({
+ height: 0,
+ margin: 0,
+ opacity: 0
+ }),
+ animate(timing + ' .25s ' + easing, style({
+ height: '*',
+ margin: '1rem 0 0.5rem 0',
+ opacity: 1
+ }))
+ ]),
+ transition(':leave', [
+ style({
+ overflowY: 'hidden'
+ }),
+ animate(timing + ' ' + easing, style({
+ height: 0,
+ margin: 0,
+ opacity: 0
+ }))
+ ])
+])
\ No newline at end of file
diff --git a/projects/go-lib/src/lib/components/go-search/go-search.component.html b/projects/go-lib/src/lib/components/go-search/go-search.component.html
new file mode 100644
index 000000000..57e094975
--- /dev/null
+++ b/projects/go-lib/src/lib/components/go-search/go-search.component.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+ {{ goSearchService.noResultsMessage }}
+
+
+
\ No newline at end of file
diff --git a/projects/go-lib/src/lib/components/go-search/go-search.component.scss b/projects/go-lib/src/lib/components/go-search/go-search.component.scss
new file mode 100644
index 000000000..014d0addd
--- /dev/null
+++ b/projects/go-lib/src/lib/components/go-search/go-search.component.scss
@@ -0,0 +1,143 @@
+@import '~@tangoe/gosheets/base/variables';
+@import '~@tangoe/gosheets/base/mixins';
+
+.go-search {
+ position: relative;
+}
+
+.go-search__container {
+ background: $theme-light-bg;
+ border: 1px solid $theme-light-border;
+ border-radius: 1rem;
+ box-shadow: none;
+ color: $theme-light-border;
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ top: calc(50% - ((1.875rem + 2px) / 2));
+ // height of input + border, halfed
+ width: 600px;
+ @include transition(all);
+
+ &:hover {
+ background: lighten($theme-light-app-bg, 3%);
+ }
+}
+
+.go-search__container--active {
+ border: 0;
+ box-shadow: $global-box-shadow;
+ padding: 0.5rem;
+ top: calc(50% - (2.875rem / 2));
+ // height of input with padding, halfed
+
+ &:hover {
+ background: $theme-light-bg;
+ }
+}
+
+.go-search__field {
+ align-items: center;
+ display: flex;
+ @include transition(all);
+}
+
+.go-search__submit {
+ align-items: center;
+ background: transparent;
+ border: 0;
+ color: $theme-light-border;
+ display: flex;
+ font-size: 1rem;
+ padding: 0 0.5rem;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &:active, &:focus {
+ outline: none;
+ }
+}
+
+.go-search__icon {
+ height: 1rem;
+}
+
+.go-search__input {
+ background: transparent;
+ border: 0;
+ flex: 1;
+ font-family: $base-font-stack;
+ font-size: 0.875rem;
+ font-weight: 300;
+ letter-spacing: 0.02rem;
+ min-width: 250px;
+ padding: .5rem .5rem .5rem 0;
+
+ &:-ms-input-placeholder {
+ color: $theme-light-color;
+ }
+
+ &::placeholder {
+ color: $theme-light-color;
+ }
+
+ &:active, &:focus {
+ outline: none;
+ }
+}
+
+.go-search__loader-container {
+ display: flex;
+ height: calc(4rem + 50px);
+ justify-content: center;
+ overflow: hidden;
+ padding: 2rem;
+ position: relative;
+}
+
+.go-search__loader {
+ position: absolute;
+}
+
+.go-search__results {
+ background: $theme-light-bg;
+ color: $theme-light-color;
+ font-size: 0.875rem;
+ max-height: 400px;
+ margin: 1rem 0 0.5rem 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ padding: 0 0.5rem;
+}
+
+/**
+* This section should be included in gosheets as a global change.
+* Until that happens, we should keep this here.
+**/
+::-webkit-scrollbar {
+ height: 12px;
+ width: 12px;
+
+ @media (max-width: 768px) {
+ height: 0 !important;
+ width: 0 !important;
+ }
+}
+
+::-webkit-scrollbar-track {
+ background-color: $theme-light-app-bg;
+ border-radius: 6px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: $base-light-secondary;
+ border: 2px solid $theme-light-app-bg;
+ border-radius: 6px;
+ @include transition(all);
+
+ &:hover {
+ background: $ui-color-neutral-gradient;
+ }
+}
diff --git a/projects/go-lib/src/lib/components/go-search/go-search.component.spec.ts b/projects/go-lib/src/lib/components/go-search/go-search.component.spec.ts
new file mode 100644
index 000000000..75e7cf4c8
--- /dev/null
+++ b/projects/go-lib/src/lib/components/go-search/go-search.component.spec.ts
@@ -0,0 +1,36 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+
+import { GoSearchComponent } from './go-search.component';
+
+import { GoIconModule } from '../go-icon/go-icon.module';
+import { GoLoaderModule } from '../go-loader/go-loader.module';
+
+describe('GoSearchComponent', () => {
+ let component: GoSearchComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ GoSearchComponent ],
+ imports: [
+ CommonModule,
+ GoIconModule,
+ GoLoaderModule,
+ ReactiveFormsModule
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GoSearchComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/projects/go-lib/src/lib/components/go-search/go-search.component.ts b/projects/go-lib/src/lib/components/go-search/go-search.component.ts
new file mode 100644
index 000000000..98a9e3578
--- /dev/null
+++ b/projects/go-lib/src/lib/components/go-search/go-search.component.ts
@@ -0,0 +1,82 @@
+import { Component, ElementRef, HostListener, OnInit } from '@angular/core';
+import { AnimationEvent } from '@angular/animations';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
+
+import { searchLoaderAnim, searchResultsAnim } from '../../animations/search.animation';
+import { GoSearchService } from './go-search.service';
+
+@Component({
+ selector: 'go-search',
+ templateUrl: './go-search.component.html',
+ styleUrls: ['./go-search.component.scss'],
+ animations: [searchLoaderAnim, searchResultsAnim]
+})
+export class GoSearchComponent implements OnInit {
+
+ goSearchForm: FormGroup;
+ searchActive: boolean = false;
+ resultsOverflow: string = 'hidden';
+
+ @HostListener('document:click') onDocumentClick(event) {
+ this.closeSearchEvent(event);
+ }
+
+ constructor(
+ public goSearchService: GoSearchService,
+ private elementRef: ElementRef,
+ private fb: FormBuilder
+ ) {
+ this.goSearchForm = this.fb.group({
+ term: ''
+ });
+ }
+
+ ngOnInit(): void {
+ this.goSearchForm.valueChanges.pipe(
+ debounceTime(500),
+ distinctUntilChanged()
+ ).subscribe(changes => {
+ if (changes['term'].length >= this.goSearchService.termLength) {
+ this.goSearchService.showNoResultsMessage = false;
+ this.goSearchService.showLoader = true;
+ this.goSearchService.updateSearchTerm(changes['term']);
+ } else {
+ this.goSearchService.showNoResultsMessage = false;
+ this.goSearchService.hasResults = false;
+ this.goSearchService.showLoader = false;
+ }
+ });
+ }
+
+ resultsStarted(event: AnimationEvent): void {
+ this.resultsOverflow = 'hidden';
+ }
+
+ resultsEnded(event: AnimationEvent): void {
+ this.resultsOverflow = event.toState === null ? 'auto' : 'hidden';
+ }
+
+ toggleActive(): void {
+ this.searchActive = true;
+ }
+
+ leaveInput(event: any): void {
+ if (!this.elementRef.nativeElement.contains(event.relatedTarget)) {
+ this.closeSearch();
+ }
+ }
+
+ closeSearchEvent(event: any): void {
+ if (event && !this.elementRef.nativeElement.contains(event.target)) {
+ this.closeSearch();
+ }
+ }
+
+ closeSearch(): void {
+ this.searchActive = false;
+ this.goSearchService.hasResults = false;
+ this.goSearchService.showNoResultsMessage = false;
+ this.goSearchForm.reset('', {onlySelf: true, emitEvent: false});
+ }
+}
diff --git a/projects/go-lib/src/lib/components/go-search/go-search.module.ts b/projects/go-lib/src/lib/components/go-search/go-search.module.ts
new file mode 100644
index 000000000..174a88911
--- /dev/null
+++ b/projects/go-lib/src/lib/components/go-search/go-search.module.ts
@@ -0,0 +1,25 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ReactiveFormsModule } from '@angular/forms';
+import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import { GoIconModule } from '../go-icon/go-icon.module';
+import { GoLoaderModule } from '../go-loader/go-loader.module';
+
+import { GoSearchComponent } from './go-search.component';
+
+@NgModule({
+ declarations: [GoSearchComponent],
+ imports: [
+ BrowserAnimationsModule,
+ BrowserModule,
+ CommonModule,
+ GoIconModule,
+ GoLoaderModule,
+ ReactiveFormsModule
+ ],
+ exports: [GoSearchComponent]
+})
+
+export class GoSearchModule { }
diff --git a/projects/go-lib/src/lib/components/go-search/go-search.service.ts b/projects/go-lib/src/lib/components/go-search/go-search.service.ts
new file mode 100644
index 000000000..97528cdd5
--- /dev/null
+++ b/projects/go-lib/src/lib/components/go-search/go-search.service.ts
@@ -0,0 +1,67 @@
+import { Injectable } from '@angular/core';
+
+import { Subject } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class GoSearchService {
+
+ /**
+ * Whether or not the service making requests returned results
+ */
+ hasResults: boolean = false;
+
+ /**
+ * The message to be shown when no results are returned from the server that match the search term
+ */
+ noResultsMessage: string = 'No Results Found';
+
+ /**
+ * Whether or not to show the noResultsMessage
+ */
+ showNoResultsMessage: boolean = false;
+
+ /**
+ * Whether or not to show the loader in the search bar
+ */
+ showLoader: boolean = false;
+
+ /**
+ * The term entered by the user to search
+ */
+ searchTerm: Subject = new Subject();
+
+ /**
+ * Minimum number of characters to trigger a search
+ */
+ termLength: number = 3;
+
+ /**
+ * Use this method to update the search term
+ * @param term {string} The search term entered by the user
+ */
+ updateSearchTerm(term: string): void {
+ this.searchTerm.next(term);
+ }
+
+ /**
+ * Use this method when you get a response from
+ * the server that was successful, **with results**
+ */
+ successResponse(): void {
+ this.hasResults = true;
+ this.showLoader = false;
+ this.showNoResultsMessage = false;
+ }
+
+ /**
+ * Use this method when you get a response from
+ * the server that was successful, but **with no results**
+ */
+ notFoundResponse(): void {
+ this.hasResults = false;
+ this.showLoader = false;
+ this.showNoResultsMessage = true;
+ }
+}
diff --git a/projects/go-lib/src/lib/go-shared.module.ts b/projects/go-lib/src/lib/go-shared.module.ts
index 75643cd45..6f1a220a5 100644
--- a/projects/go-lib/src/lib/go-shared.module.ts
+++ b/projects/go-lib/src/lib/go-shared.module.ts
@@ -5,6 +5,7 @@ import { GoCardModule } from './components/go-card/go-card.module';
import { GoIconModule } from './components/go-icon/go-icon.module';
import { GoLoaderModule } from './components/go-loader/go-loader.module';
import { GoModalModule } from './components/go-modal/go-modal.module';
+import { GoSearchModule } from './components/go-search/go-search.module';
import { GoSideNavModule } from './components/go-side-nav/go-side-nav.module';
import { GoTableModule } from './components/go-table/go-table.module';
import { GoToastModule } from './components/go-toast/go-toast.module';
@@ -18,6 +19,7 @@ import { GoToasterModule } from './components/go-toaster/go-toaster.module';
GoIconModule,
GoLoaderModule,
GoModalModule,
+ GoSearchModule,
GoSideNavModule,
GoTableModule,
GoToastModule,
@@ -30,6 +32,7 @@ import { GoToasterModule } from './components/go-toaster/go-toaster.module';
GoIconModule,
GoLoaderModule,
GoModalModule,
+ GoSearchModule,
GoSideNavModule,
GoTableModule,
GoToastModule,
diff --git a/projects/go-lib/src/public_api.ts b/projects/go-lib/src/public_api.ts
index e160528fe..455a1be17 100644
--- a/projects/go-lib/src/public_api.ts
+++ b/projects/go-lib/src/public_api.ts
@@ -36,6 +36,11 @@ export * from './lib/components/go-off-canvas/go-off-canvas.component';
export * from './lib/components/go-off-canvas/go-off-canvas.module';
export * from './lib/components/go-off-canvas/go-off-canvas.service';
+// Search
+export * from './lib/components/go-search/go-search.component';
+export * from './lib/components/go-search/go-search.module';
+export * from './lib/components/go-search/go-search.service';
+
// Side Nav
export * from './lib/components/go-side-nav/go-side-nav.module';
export * from './lib/components/go-side-nav/nav-group.model';
diff --git a/projects/go-tester/src/app/app.component.html b/projects/go-tester/src/app/app.component.html
index 1dc558219..57ea07478 100644
--- a/projects/go-tester/src/app/app.component.html
+++ b/projects/go-tester/src/app/app.component.html
@@ -16,6 +16,15 @@
+
+
Loader
diff --git a/projects/go-tester/src/app/app.component.ts b/projects/go-tester/src/app/app.component.ts
index 874af89c7..abdadc835 100644
--- a/projects/go-tester/src/app/app.component.ts
+++ b/projects/go-tester/src/app/app.component.ts
@@ -67,13 +67,13 @@ export class AppComponent implements OnInit {
totalCount: data.totalCount
});
this.tableLoading = false;
- })
+ });
- setTimeout(() => {
- this.goToasterService.toastInfo({ message: 'Check this out'});
- this.goToasterService.toastSuccess({message: 'Check this out' });
- this.goToasterService.toastError({ message: 'Check this out' });
- }, 1500);
+ // setTimeout(() => {
+ // this.goToasterService.toastInfo({ message: 'Check this out'});
+ // this.goToasterService.toastSuccess({message: 'Check this out' });
+ // this.goToasterService.toastError({ message: 'Check this out' });
+ // }, 1500);
}
stopLoaderAnimation() {
@@ -92,7 +92,7 @@ export class AppComponent implements OnInit {
setTimeout(() => {
currentTableConfig.tableData = data.results;
currentTableConfig.totalCount = data.totalCount;
-
+
this.tableConfig = currentTableConfig;
this.tableLoading = false;
}, 2000);
diff --git a/projects/go-tester/src/app/app.module.ts b/projects/go-tester/src/app/app.module.ts
index c58a8dd2f..5a30b9791 100644
--- a/projects/go-tester/src/app/app.module.ts
+++ b/projects/go-tester/src/app/app.module.ts
@@ -12,20 +12,22 @@ import {
GoIconModule,
GoLoaderModule,
GoOffCanvasModule,
+ GoSearchModule,
GoSideNavModule,
GoTableModule,
- GoToastModule,
- GoToasterModule
+ GoToasterModule,
+ GoToastModule
} from '../../../go-lib/src/public_api';
import { AppComponent } from './app.component';
import { AppService } from './app.service';
-
+import { SearchTestComponent } from './components/search-test.component';
@NgModule({
declarations: [
AppComponent,
- DummyComponent
+ DummyComponent,
+ SearchTestComponent
],
imports: [
BrowserModule,
@@ -35,6 +37,7 @@ import { AppService } from './app.service';
GoIconModule,
GoLoaderModule,
GoOffCanvasModule,
+ GoSearchModule,
GoSideNavModule,
GoTableModule,
GoToastModule,
diff --git a/projects/go-tester/src/app/app.service.ts b/projects/go-tester/src/app/app.service.ts
index 5684618c5..f33790506 100644
--- a/projects/go-tester/src/app/app.service.ts
+++ b/projects/go-tester/src/app/app.service.ts
@@ -31,6 +31,14 @@ export class AppService {
}));
}
+ getMockSearch(term: string) {
+ return this.http.get("../assets/MOCK_DATA_1000.json").pipe(map(data => {
+ return data.filter(item => {
+ return item.id.toString().includes(term) || item.name.first.includes(term) || item.name.last.includes(term) || item.email.includes(term);
+ })
+ }))
+ }
+
/***** Private Methods *****/
private paginateData(paging: GoTablePageConfig, results: any[]) : any[] {
return results.slice(paging.offset, paging.offset + paging.perPage);
diff --git a/projects/go-tester/src/app/components/search-test.component.html b/projects/go-tester/src/app/components/search-test.component.html
new file mode 100644
index 000000000..e60e961ed
--- /dev/null
+++ b/projects/go-tester/src/app/components/search-test.component.html
@@ -0,0 +1,10 @@
+
+
+ {{ item.id }} |
+ {{ item.name.first }} |
+ {{ item.name.last }} |
+ {{ item.email }} |
+ {{ item.gender }} |
+ {{ item.ip_address }} |
+
+
\ No newline at end of file
diff --git a/projects/go-tester/src/app/components/search-test.component.ts b/projects/go-tester/src/app/components/search-test.component.ts
new file mode 100644
index 000000000..2088b225d
--- /dev/null
+++ b/projects/go-tester/src/app/components/search-test.component.ts
@@ -0,0 +1,39 @@
+import { Component, OnInit } from '@angular/core';
+
+import { GoSearchService } from '../../../../go-lib/src/public_api';
+
+import { AppService } from '../app.service';
+
+@Component({
+ selector: 'app-search-test',
+ templateUrl: './search-test.component.html'
+})
+export class SearchTestComponent implements OnInit {
+
+ results: any[];
+
+ constructor(
+ private searchService: GoSearchService,
+ private appService: AppService
+ ) { }
+
+ ngOnInit() {
+ this.searchService.searchTerm.subscribe(searchTerm => {
+ // this section is dependent upon what the data looks like
+ // the loader and hasResults should be updated accordingly
+ this.appService
+ .getMockSearch(searchTerm)
+ .subscribe(results => {
+ setTimeout(() => {
+ if (results.length === 0) {
+ this.results = null;
+ this.searchService.notFoundResponse();
+ } else {
+ this.results = results;
+ this.searchService.successResponse();
+ }
+ }, 1000);
+ });
+ });
+ }
+}