This repository has been archived by the owner on Dec 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 401
feat(navbar): Add basic search bar component to site. #134
Open
riavalon
wants to merge
1
commit into
angular:main
Choose a base branch
from
riavalon:feat-search-bar
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+327
−17
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './navbar'; | ||
export * from './searchbar'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './searchbar'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<div class="docs-search-input-container"> | ||
<input | ||
placeholder="Search" | ||
type="text" | ||
class="docs-search-input" | ||
(focus)="toggleIsExpanded()" | ||
(blur)="toggleIsExpanded($event.relatedTarget)" | ||
(keyup.enter)="handlePlainSearch($event.target.value.toLowerCase())" | ||
[mdAutocomplete]="auto" | ||
[formControl]="searchControl"> | ||
<md-icon class="docs-search-icon">search</md-icon> | ||
</div> | ||
|
||
<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn"> | ||
<md-option | ||
*ngFor="let item of filteredSuggestions | async" | ||
(click)="handlePlainSearch(item.name.toLowerCase())" | ||
[value]="item"> | ||
{{item.name}} | ||
</md-option> | ||
</md-autocomplete> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
@mixin color-placeholder() { | ||
-webkit-font-smoothing: antialiased; | ||
color: white; | ||
} | ||
|
||
:host { | ||
position: relative; | ||
flex: 2; | ||
|
||
* { | ||
box-sizing: border-box; | ||
} | ||
|
||
&.docs-expanded .docs-search-input-container { | ||
width: 100%; | ||
} | ||
|
||
.docs-search-input-container { | ||
display: block; | ||
position: relative; | ||
margin-left: auto; | ||
height: 100%; | ||
width: 200px; | ||
transition: width .2s ease; | ||
|
||
.docs-search-icon { | ||
position: absolute; | ||
left: 15px; top: 50%; | ||
transform: translateY(-50%); | ||
height: 28px; | ||
width: 28px; | ||
} | ||
} | ||
|
||
.docs-search-input { | ||
background: rgba(255, 255, 255, 0.4); | ||
border: none; | ||
border-radius: 2px; | ||
color: white; | ||
font-size: 18px; | ||
height: 95%; | ||
line-height: 95%; | ||
padding-left: 50px; | ||
position: relative; | ||
transition: width .2s ease; | ||
width: 100%; | ||
|
||
/* Set placeholder text to be white */ | ||
&::-webkit-input-placeholder { @include color-placeholder(); } /* Chrome/Opera/Safari */ | ||
&::-moz-placeholder { @include color-placeholder(); } /* Firefox 19+ */ | ||
&:-moz-placeholder { @include color-placeholder(); } /* Firefox 18- */ | ||
&:ms-input-placeholder { @include color-placeholder(); } /* IE 10+ */ | ||
|
||
&:focus { | ||
outline: none; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {TestBed, inject, async, ComponentFixture} from '@angular/core/testing'; | ||
import {Router, RouterModule} from '@angular/router'; | ||
import {MaterialModule} from '@angular/material'; | ||
import {ReactiveFormsModule, FormControl} from '@angular/forms'; | ||
|
||
import {DocumentationItems, DocItem} from '../../documentation-items/documentation-items'; | ||
import {SearchBar} from './searchbar'; | ||
|
||
const mockRouter = { | ||
navigate: jasmine.createSpy('navigate'), | ||
navigateByUrl: jasmine.createSpy('navigateByUrl') | ||
}; | ||
|
||
const testDocItem = { | ||
id: 'test-doc-item', | ||
name: 'TestingExample', | ||
examples: ['test-examples'] | ||
}; | ||
|
||
|
||
class MockDocumentationItems extends DocumentationItems { | ||
getAllItems(): DocItem[] { return [testDocItem]; } | ||
} | ||
|
||
|
||
describe('SearchBar', () => { | ||
let fixture: ComponentFixture<SearchBar>; | ||
let component: SearchBar; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [RouterModule, ReactiveFormsModule, MaterialModule], | ||
declarations: [SearchBar], | ||
providers: [ | ||
{provide: DocumentationItems, useClass: MockDocumentationItems}, | ||
{provide: Router, useValue: mockRouter}, | ||
], | ||
}); | ||
|
||
TestBed.compileComponents(); | ||
fixture = TestBed.createComponent(SearchBar); | ||
component = fixture.componentInstance; | ||
component.searchControl = new FormControl(''); | ||
fixture.detectChanges(); | ||
})); | ||
|
||
afterEach(() => { | ||
(<any>component._router.navigateByUrl).calls.reset(); | ||
}); | ||
|
||
it('should toggle isExpanded', () => { | ||
expect(component._isExpanded).toBe(false); | ||
component.toggleIsExpanded(); | ||
expect(component._isExpanded).toBe(true); | ||
}); | ||
|
||
describe('Filter Search Suggestions', () => { | ||
it('should return all items matching search query', () => { | ||
const query = 'testing'; | ||
const result = component.filterSearchSuggestions(query); | ||
expect(result).toEqual([testDocItem]); | ||
}); | ||
|
||
it('should return empty list if no items match', () => { | ||
const query = 'does not exist'; | ||
const result = component.filterSearchSuggestions(query); | ||
expect(result).toEqual([]); | ||
}); | ||
}); | ||
|
||
describe('Navigate', () => { | ||
|
||
it('should take an id and navigate to the given route', () => { | ||
component._navigate('button-toggle'); | ||
expect(component._router.navigateByUrl).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should not navigate if no id is given', () => { | ||
component._navigate(''); | ||
expect(component._router.navigateByUrl).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
it('should show a snackbar error', () => { | ||
spyOn(component._snackBar, 'open'); | ||
component._showError(); | ||
expect(component._snackBar.open).toHaveBeenCalled(); | ||
expect(component._snackBar.open).toHaveBeenCalledWith( | ||
'No search results found.', | ||
null, {duration: 3000}); | ||
}); | ||
|
||
it('should return the proper display value for form control', () => { | ||
const result = component.displayFn(testDocItem); | ||
expect(result).toEqual(testDocItem.name); | ||
}); | ||
}); | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import {Component, ViewChild} from '@angular/core'; | ||
import {MdAutocompleteTrigger, MdSnackBar} from '@angular/material'; | ||
import {Router} from '@angular/router'; | ||
import {FormControl} from '@angular/forms'; | ||
|
||
import {Observable} from 'rxjs/Observable'; | ||
import {Subscription} from 'rxjs/Subscription'; | ||
import 'rxjs/add/operator/mergeMap'; | ||
|
||
import {DocumentationItems, DocItem} from '../../documentation-items/documentation-items'; | ||
|
||
|
||
@Component({ | ||
selector: 'search-bar-component', | ||
templateUrl: './searchbar.html', | ||
styleUrls: ['./searchbar.scss'], | ||
host: { | ||
'[class.docs-expanded]': '_isExpanded' | ||
} | ||
}) | ||
|
||
export class SearchBar { | ||
|
||
@ViewChild(MdAutocompleteTrigger) | ||
private _autocompleteTrigger: MdAutocompleteTrigger; | ||
|
||
allDocItems: DocItem[]; | ||
filteredSuggestions: Observable<DocItem[]>; | ||
searchControl: FormControl = new FormControl(''); | ||
subscription: Subscription; | ||
|
||
_isExpanded: boolean = false; | ||
|
||
constructor( | ||
public _docItems: DocumentationItems, | ||
public _router: Router, | ||
public _snackBar: MdSnackBar | ||
) { | ||
this.allDocItems = _docItems.getAllItems(); | ||
this.filteredSuggestions = this.searchControl.valueChanges | ||
.startWith(null) | ||
.map(item => item ? this.filterSearchSuggestions(item) : this.allDocItems.slice()); | ||
} | ||
|
||
// This handles the user interacting with the autocomplete panel clicks or keyboard. | ||
ngAfterViewInit() { | ||
// We listen to the changes on `filteredSuggestions in order to | ||
// listen to the latest _autocompleteTrigger.optionSelections | ||
this.subscription = this.filteredSuggestions | ||
.flatMap(_ => this._autocompleteTrigger.optionSelections) | ||
.subscribe(evt => this._navigate(evt.source.value.id)); | ||
} | ||
|
||
ngOnDestroy() { | ||
if (this.subscription) { this.subscription.unsubscribe(); } | ||
} | ||
|
||
toggleIsExpanded(evt?: any) { | ||
if (!this._isExpanded && evt === null || evt && evt.tagName === 'MD-OPTION') { | ||
// input not expanded and blurring || input is expanded and we clicked on an option | ||
return; | ||
} else if (this._isExpanded && evt === undefined) { | ||
// input is expanded and we are not blurring | ||
this._delayDropdown(false); | ||
} else { | ||
// defualt behaviour: not expanded and focusing || expanded and blurring | ||
this._delayDropdown(this._isExpanded); | ||
this._isExpanded = !this._isExpanded; | ||
} | ||
} | ||
|
||
displayFn(item: DocItem) { | ||
return item.name; | ||
} | ||
|
||
filterSearchSuggestions(searchTerm): DocItem[] { | ||
return this.allDocItems.filter(item => new RegExp(`^${searchTerm}`, 'gi').test(item.name)); | ||
} | ||
|
||
handlePlainSearch(searchTerm) { | ||
const item = this.allDocItems.find(item => item.name.toLowerCase() === searchTerm); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Partial matches should also jump to the first result. E.g., typing "Date" and hitting enter should jump to the datepicker. |
||
return item ? | ||
this._navigate(item.id) : | ||
this.navigateToClosestMatch(searchTerm); | ||
} | ||
|
||
navigateToClosestMatch(term) { | ||
const item = this.filterSearchSuggestions(term)[0]; | ||
item ? | ||
this._navigate(item.id) : | ||
this._showError(); | ||
} | ||
|
||
_navigate(id) { | ||
this._resetSearch(); | ||
return id ? this._router.navigateByUrl(`/components/component/${id}`) : null; | ||
} | ||
|
||
_resetSearch() { | ||
this.searchControl.reset(); | ||
this.searchControl.setValue(''); | ||
} | ||
|
||
_showError() { | ||
this._snackBar.open('No search results found.', null, {duration: 3000}); | ||
} | ||
|
||
_delayDropdown(isExpanded: boolean) { | ||
if (isExpanded) { | ||
this._autocompleteTrigger.closePanel(); | ||
} else { | ||
this._autocompleteTrigger.closePanel(); | ||
setTimeout(() => this._autocompleteTrigger.openPanel(), 210); | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
autoprefixer is supposed to be built into the angular-cli, but I'm not sure if there's some specific configuration needed to turn it on
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did see that it was auto-prefixing some styles, but the input placeholder one unfortunately wasn't being done for some reason.