From 8783f6011df9e253c00ec9fa6c85826ecf627c4b Mon Sep 17 00:00:00 2001 From: Ed Morales Date: Thu, 27 Oct 2016 08:58:15 -0700 Subject: [PATCH] Feature/search box (#122) * added search-input atomic component for search-boxes * added clear event to search-input * renamed to search dir and added blur event * change search-box to search package * changed input into type="search" and added searchDebounce event * search-box molecule component * added search module * added flex="none" scss rule * added CovalentSearchModule in docs modules * added doc files * added demo for search input and search box * updated docs in demo for search components * added docblocks to code API * updated README.md * added showUnderline input * feature(search): back icon attribute * update(search): getter property instead of private member * added backIcon to readme * update(search-box): hide float label * fix(search-box): only hide focused label * updating backIcon desc --- src/app/app.module.ts | 2 + .../components/components.component.ts | 5 + .../components/components.module.ts | 4 + .../components/components.routes.ts | 4 + .../components/overview/overview.component.ts | 5 + .../components/search/search.component.html | 113 ++++++++++++++++++ .../components/search/search.component.scss | 0 .../components/search/search.component.ts | 85 +++++++++++++ src/platform/core/styles/_layout.scss | 3 + src/platform/search/README.md | 69 +++++++++++ src/platform/search/index.ts | 3 + src/platform/search/package.json | 37 ++++++ .../search-box/search-box.component.html | 15 +++ .../search-box/search-box.component.scss | 14 +++ .../search/search-box/search-box.component.ts | 109 +++++++++++++++++ .../search-input/search-input.component.html | 17 +++ .../search-input/search-input.component.scss | 9 ++ .../search-input/search-input.component.ts | 106 ++++++++++++++++ src/platform/search/search.module.ts | 33 +++++ 19 files changed, 633 insertions(+) create mode 100644 src/app/components/components/search/search.component.html create mode 100644 src/app/components/components/search/search.component.scss create mode 100644 src/app/components/components/search/search.component.ts create mode 100644 src/platform/search/README.md create mode 100644 src/platform/search/index.ts create mode 100644 src/platform/search/package.json create mode 100644 src/platform/search/search-box/search-box.component.html create mode 100644 src/platform/search/search-box/search-box.component.scss create mode 100644 src/platform/search/search-box/search-box.component.ts create mode 100644 src/platform/search/search-input/search-input.component.html create mode 100644 src/platform/search/search-input/search-input.component.scss create mode 100644 src/platform/search/search-input/search-input.component.ts create mode 100644 src/platform/search/search.module.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 530395aa61..3b7a527465 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,6 +18,7 @@ import { CovalentJsonFormatterModule } from '../platform/json-formatter'; import { CovalentChipsModule } from '../platform/chips'; import { CovalentChartsModule } from '../platform/charts'; import { CovalentDataTableModule } from '../platform/data-table'; +import { CovalentSearchModule } from '../platform/search'; @NgModule({ declarations: [ @@ -39,6 +40,7 @@ import { CovalentDataTableModule } from '../platform/data-table'; CovalentChipsModule.forRoot(), CovalentChartsModule.forRoot(), CovalentDataTableModule.forRoot(), + CovalentSearchModule.forRoot(), appRoutes, ], // modules needed to run this module providers: [ diff --git a/src/app/components/components/components.component.ts b/src/app/components/components/components.component.ts index 0bcd0aa84b..c223f29e66 100644 --- a/src/app/components/components/components.component.ts +++ b/src/app/components/components/components.component.ts @@ -72,6 +72,11 @@ export class ComponentsComponent { icon: 'show_chart', route: 'charts', title: 'Charts', + }, { + description: 'Search and filter items', + icon: 'search', + route: 'search', + title: 'Search', }, { description: 'Responsive service & directive', icon: 'devices', diff --git a/src/app/components/components/components.module.ts b/src/app/components/components/components.module.ts index c19596ab18..228718607a 100644 --- a/src/app/components/components/components.module.ts +++ b/src/app/components/components/components.module.ts @@ -19,6 +19,7 @@ import { DirectivesComponent } from './directives/directives.component'; import { PipesComponent } from './pipes/pipes.component'; import { ChartsDemoComponent } from './charts/charts.component'; import { DataTableDemoComponent } from './data-table/data-table.component'; +import { SearchDemoComponent } from './search/search.component'; import { MaterialComponentsComponent } from './material-components/material-components.component'; import { CovalentCoreModule } from '../../../platform/core'; @@ -29,6 +30,7 @@ import { CovalentJsonFormatterModule } from '../../../platform/json-formatter'; import { CovalentChipsModule } from '../../../platform/chips'; import { CovalentChartsModule } from '../../../platform/charts'; import { CovalentDataTableModule } from '../../../platform/data-table'; +import { CovalentSearchModule } from '../../../platform/search'; @NgModule({ declarations: [ @@ -49,6 +51,7 @@ import { CovalentDataTableModule } from '../../../platform/data-table'; PipesComponent, ChartsDemoComponent, DataTableDemoComponent, + SearchDemoComponent, MaterialComponentsComponent, ], imports: [ @@ -60,6 +63,7 @@ import { CovalentDataTableModule } from '../../../platform/data-table'; CovalentChipsModule.forRoot(), CovalentChartsModule.forRoot(), CovalentDataTableModule.forRoot(), + CovalentSearchModule.forRoot(), componentsRoutes, ], }) diff --git a/src/app/components/components/components.routes.ts b/src/app/components/components/components.routes.ts index c617c8adbd..93a6291fe0 100644 --- a/src/app/components/components/components.routes.ts +++ b/src/app/components/components/components.routes.ts @@ -17,6 +17,7 @@ import { DirectivesComponent } from './directives/directives.component'; import { PipesComponent } from './pipes/pipes.component'; import { ChartsDemoComponent } from './charts/charts.component'; import { DataTableDemoComponent } from './data-table/data-table.component'; +import { SearchDemoComponent } from './search/search.component'; import { MaterialComponentsComponent } from './material-components/material-components.component'; const routes: Routes = [{ @@ -59,6 +60,9 @@ const routes: Routes = [{ }, { component: DialogsDemoComponent, path: 'dialogs', + }, { + component: SearchDemoComponent, + path: 'search', }, { component: DirectivesComponent, path: 'directives', diff --git a/src/app/components/components/overview/overview.component.ts b/src/app/components/components/overview/overview.component.ts index a304b2a906..ed945bc1af 100644 --- a/src/app/components/components/overview/overview.component.ts +++ b/src/app/components/components/overview/overview.component.ts @@ -66,6 +66,11 @@ export class ComponentsOverviewComponent { icon: 'show_chart', route: 'charts', title: 'Charts', + }, { + color: 'lime-700', + icon: 'search', + route: 'search', + title: 'Search', }, { color: 'red-700', icon: 'devices', diff --git a/src/app/components/components/search/search.component.html b/src/app/components/components/search/search.component.html new file mode 100644 index 0000000000..8c7aea58dc --- /dev/null +++ b/src/app/components/components/search/search.component.html @@ -0,0 +1,113 @@ + + Search + Search and filter items + + +
+ + search Enter/Clear + keyboard Debounce + +
+

Search Input

+

Search Value: {{searchInputTerm || 'Empty'}}

+
+ + +
+

Search Box

+

Search Value: {{searchBoxTerm || 'Empty'}}

+
+ +
+ Card + + + +
+ + card content +
+
+

Search Box in Toolbar

+ + + + +
+ + + + +
+ + TdSearchInputComponent + How to use this component + + +

]]>

+

Use ]]> element to generate a search input with its animated cancel button.

+

Properties:

+

The ]]> component has {{searchInputAttrs.length}} properties:

+ + + +

Example:

+

HTML:

+ + + + ]]> + +

Setup:

+

Import the [CovalentSearchModule] using the forRoot() method in your NgModule:

+ + + +
+
+ + TdSearchBoxComponent + How to use this component + + +

]]>

+

Use ]]> element to generate a search box with animations.

+

Properties:

+

The ]]> component has {{searchBoxAttrs.length}} properties:

+ + + +

Example:

+

HTML:

+ + + + ]]> + +
+
\ No newline at end of file diff --git a/src/app/components/components/search/search.component.scss b/src/app/components/components/search/search.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/components/components/search/search.component.ts b/src/app/components/components/search/search.component.ts new file mode 100644 index 0000000000..990edbb575 --- /dev/null +++ b/src/app/components/components/search/search.component.ts @@ -0,0 +1,85 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'search-demo', + styleUrls: ['search.component.scss'], + templateUrl: 'search.component.html', +}) +export class SearchDemoComponent { + + searchInputAttrs: Object[] = [{ + description: `Debounce timeout between keypresses. Defaults to 400.`, + name: 'debounce?', + type: 'number', + }, { + description: `Placeholder for the underlying input component.`, + name: 'placeholder?', + type: 'string', + }, { + description: `Sets if the input underline should be visible. Defaults to 'false'.`, + name: 'showUnderline?', + type: 'boolean', + }, { + description: `Event emitted after the [debounce] timeout.`, + name: 'searchDebounce', + type: 'function($event)', + }, { + description: `Event emitted after the key enter has been pressed.`, + name: 'search', + type: 'function($event)', + }, { + description: `Event emitted after the clear icon has been clicked.`, + name: 'clear', + type: 'function($event)', + }]; + + searchBoxAttrs: Object[] = [{ + description: `Debounce timeout between keypresses. Defaults to 400.`, + name: 'debounce?', + type: 'number', + }, { + description: `Placeholder for the underlying input component.`, + name: 'placeholder?', + type: 'string', + }, { + description: `The icon used to close the search toggle, only shown when [alwaysVisible] is false. + Defaults to 'search' icon.`, + name: 'backIcon?', + type: 'string', + }, { + description: `Sets if the input underline should be visible. Defaults to 'false'.`, + name: 'showUnderline?', + type: 'boolean', + }, { + description: `Sets if the input should always be visible. Defaults to 'false'.`, + name: 'alwaysVisible?', + type: 'boolean', + }, { + description: `Event emitted after the [debounce] timeout.`, + name: 'searchDebounce', + type: 'function($event)', + }, { + description: `Event emitted after the key enter has been pressed.`, + name: 'search', + type: 'function($event)', + }, { + description: `Event emitted after the clear icon has been clicked.`, + name: 'clear', + type: 'function($event)', + }]; + + searchInputTerm: string = ''; + searchBoxTerm: string = ''; + debounce: number = 0; + alwaysVisible: boolean = false; + + modeChange(): void { + this.searchInputTerm = ''; + this.searchBoxTerm = ''; + } + + toggleAlwaysVisible(): void { + this.alwaysVisible = !this.alwaysVisible; + } + +} diff --git a/src/platform/core/styles/_layout.scss b/src/platform/core/styles/_layout.scss index 9ce772b45b..fc4dfbe29a 100644 --- a/src/platform/core/styles/_layout.scss +++ b/src/platform/core/styles/_layout.scss @@ -568,4 +568,7 @@ $layout-breakpoint-lg: 1920px !default; } } +[flex="none"] { + flex: none; +} diff --git a/src/platform/search/README.md b/src/platform/search/README.md new file mode 100644 index 0000000000..105326373a --- /dev/null +++ b/src/platform/search/README.md @@ -0,0 +1,69 @@ +# td-search-input + +`td-search-input` element to generate a search input with its animated cancel button. + +## API Summary + +Properties: + +| Name | Type | Description | +| --- | --- | --- | +| `debounce?` | `number` | Debounce timeout between keypresses. Defaults to 400. +| `placeholder?` | `string` | Placeholder for the underlying input component. +| `showUnderline?` | `boolean` | Sets if the input underline should be visible. Defaults to 'false'. +| `searchDebounce` | `function($event)` | Event emitted after the [debounce] timeout. +| `search` | `function($event)` | Event emitted after the key enter has been pressed. +| `clear?` | `function` | Event emitted after the clear icon has been clicked. + +## Usage + +Example for HTML usage: + + ```html + + + ``` + +# td-search-box + +`td-search-box` element to generate a search box with animations. + +## API Summary + +Properties: + +| Name | Type | Description | +| --- | --- | --- | +| `debounce?` | `number` | Debounce timeout between keypresses. Defaults to 400. +| `placeholder?` | `string` | Placeholder for the underlying input component. +| `backIcon?` | `string` | The icon used to close the search toggle, only shown when [alwaysVisible] is false. Defaults to 'search' icon. +| `showUnderline?` | `boolean` | Sets if the input underline should be visible. Defaults to 'false'. +| `alwaysVisible?` | `boolean` | Sets if the input should always be visible. Defaults to 'false'. +| `searchDebounce` | `function($event)` | Event emitted after the [debounce] timeout. +| `search` | `function($event)` | Event emitted after the key enter has been pressed. +| `clear?` | `function` | Event emitted after the clear icon has been clicked. + +## Usage + +Example for HTML usage: + + ```html + + + ``` + +## Setup + +Import the [CovalentSearchModule] using the forRoot() method in your NgModule: + +```typescript +import { CovalentSearchModule } from '@covalent/chips'; +@NgModule({ + imports: [ + CovalentSearchModule.forRoot(), + ... + ], + ... +}) +export class MyModule {} +``` diff --git a/src/platform/search/index.ts b/src/platform/search/index.ts new file mode 100644 index 0000000000..129f18a9ef --- /dev/null +++ b/src/platform/search/index.ts @@ -0,0 +1,3 @@ +export { TdSearchBoxComponent } from './search-box/search-box.component'; +export { TdSearchInputComponent } from './search-input/search-input.component'; +export { CovalentSearchModule } from './search.module'; diff --git a/src/platform/search/package.json b/src/platform/search/package.json new file mode 100644 index 0000000000..d22296120b --- /dev/null +++ b/src/platform/search/package.json @@ -0,0 +1,37 @@ +{ + "name": "@covalent/search", + "version": "0.8.1", + "description": "Teradata UI Platform Search Box Module", + "keywords": [ + "angular", + "components", + "reusable", + "search", + "box" + ], + "scripts": { + }, + "engines": { + "node": ">4.4 < 5", + "npm": ">= 3" + }, + "repository": { + "type": "git", + "url": "https://github.com/teradata/covalent.git" + }, + "bugs": { + "url": "https://github.com/teradata/covalent/issues" + }, + "license": "MIT", + "author": "Teradata UX", + "contributors": [ + "Kyle Ledbetter ", + "Richa Vyas ", + "Ed Morales ", + "Jason Weaver ", + "Jeremy Wilken " + ], + "dependencies": { + "@covalent/core": "0.8.1" + } +} diff --git a/src/platform/search/search-box/search-box.component.html b/src/platform/search/search-box/search-box.component.html new file mode 100644 index 0000000000..47fc092184 --- /dev/null +++ b/src/platform/search/search-box/search-box.component.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/src/platform/search/search-box/search-box.component.scss b/src/platform/search/search-box/search-box.component.scss new file mode 100644 index 0000000000..29c733d5fb --- /dev/null +++ b/src/platform/search/search-box/search-box.component.scss @@ -0,0 +1,14 @@ +:host { + display: block; +} +.td-search-box { + height: 64px; + td-search-input { + margin-left: 12px; + /deep/ { + .md-input-placeholder.md-focused { + visibility: hidden; + } + } + } +} \ No newline at end of file diff --git a/src/platform/search/search-box/search-box.component.ts b/src/platform/search/search-box/search-box.component.ts new file mode 100644 index 0000000000..43e5b97dc4 --- /dev/null +++ b/src/platform/search/search-box/search-box.component.ts @@ -0,0 +1,109 @@ +import { Component, ViewChild, Input, Output, EventEmitter, + trigger, state, style, transition, animate } from '@angular/core'; + +import { TdSearchInputComponent } from '../search-input/search-input.component'; + +@Component({ + selector: 'td-search-box', + templateUrl: 'search-box.component.html', + styleUrls: [ 'search-box.component.scss' ], + animations: [ + trigger('inputState', [ + state('false', style({ + width: '0%', + 'margin-left': '0px', + })), + state('true', style({ + width: '100%', + 'margin-left': '*', + })), + transition('0 => 1', animate('200ms ease-in')), + transition('1 => 0', animate('200ms ease-out')), + ]), + ], +}) +export class TdSearchBoxComponent { + + private _searchVisible: boolean = false; + @ViewChild(TdSearchInputComponent) private _searchInput: TdSearchInputComponent; + + get searchVisible(): boolean { + return this._searchVisible; + } + + /** + * backIcon?: string + * The icon used to close the search toggle, only shown when [alwaysVisible] is false. + * Defaults to 'search' icon. + */ + @Input('backIcon') backIcon: string = 'search'; + + /** + * showUnderline?: boolean + * Sets if the input underline should be visible. Defaults to 'false'. + */ + @Input('showUnderline') showUnderline: boolean = false; + + /** + * debounce?: number + * Debounce timeout between keypresses. Defaults to 400. + */ + @Input('debounce') debounce: number = 400; + + /** + * alwaysVisible?: boolean + * Sets if the input should always be visible. Defaults to 'false'. + */ + @Input('alwaysVisible') alwaysVisible: boolean = false; + + /** + * placeholder?: string + * Placeholder for the underlying input component. + */ + @Input('placeholder') placeholder: string; + + /** + * searchDebounce: function($event) + * Event emitted after the [debounce] timeout. + */ + @Output('searchDebounce') onSearchDebounce: EventEmitter = new EventEmitter(); + + /** + * search: function($event) + * Event emitted after the key enter has been pressed. + */ + @Output('search') onSearch: EventEmitter = new EventEmitter(); + + /** + * clear: function() + * Event emitted after the clear icon has been clicked. + */ + @Output('clear') onClear: EventEmitter = new EventEmitter(); + + /** + * Method executed when the search icon is clicked. + */ + searchClicked(): void { + if (this.alwaysVisible || !this._searchVisible) { + this._searchInput.focus(); + } + this.toggleVisibility(); + } + + toggleVisibility(): void { + this._searchVisible = !this._searchVisible; + } + + handleSearchDebounce(value: string): void { + this.onSearchDebounce.emit(value); + } + + handleSearch(value: string): void { + this.onSearch.emit(value); + } + + handleClear(): void { + this.onClear.emit(undefined); + } + +} diff --git a/src/platform/search/search-input/search-input.component.html b/src/platform/search/search-input/search-input.component.html new file mode 100644 index 0000000000..c6da1a76c8 --- /dev/null +++ b/src/platform/search/search-input/search-input.component.html @@ -0,0 +1,17 @@ +
+ + + +
\ No newline at end of file diff --git a/src/platform/search/search-input/search-input.component.scss b/src/platform/search/search-input/search-input.component.scss new file mode 100644 index 0000000000..5346d51362 --- /dev/null +++ b/src/platform/search/search-input/search-input.component.scss @@ -0,0 +1,9 @@ +.td-search-input { + height: 64px; + overflow-x: hidden; + /deep/ md-input.md-hide-underline { + .md-input-underline { + display: none; + } + } +} \ No newline at end of file diff --git a/src/platform/search/search-input/search-input.component.ts b/src/platform/search/search-input/search-input.component.ts new file mode 100644 index 0000000000..73c8e85a79 --- /dev/null +++ b/src/platform/search/search-input/search-input.component.ts @@ -0,0 +1,106 @@ +import { Component, ViewChild, OnInit, Input, Output, EventEmitter, + trigger, state, style, transition, animate } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { MdInput } from '@angular/material'; + +@Component({ + selector: 'td-search-input', + templateUrl: 'search-input.component.html', + styleUrls: [ 'search-input.component.scss' ], + animations: [ + trigger('searchState', [ + state('false', style({ + transform: 'translateX(150%)', + display: 'none', + })), + state('true', style({ + transform: 'translateX(0%)', + display: 'block', + })), + transition('0 => 1', animate('200ms ease-in')), + transition('1 => 0', animate('200ms ease-out')), + ]), + ], +}) +export class TdSearchInputComponent implements OnInit { + + @ViewChild(MdInput) private _input: MdInput; + + searchTermControl: FormControl = new FormControl(); + + /** + * showUnderline?: boolean + * Sets if the input underline should be visible. Defaults to 'false'. + */ + @Input('showUnderline') showUnderline: boolean = false; + + /** + * debounce?: number + * Debounce timeout between keypresses. Defaults to 400. + */ + @Input('debounce') debounce: number = 400; + + /** + * placeholder?: string + * Placeholder for the underlying input component. + */ + @Input('placeholder') placeholder: string; + + /** + * searchDebounce: function($event) + * Event emitted after the [debounce] timeout. + */ + @Output('searchDebounce') onSearchDebounce: EventEmitter = new EventEmitter(); + + /** + * search: function($event) + * Event emitted after the key enter has been pressed. + */ + @Output('search') onSearch: EventEmitter = new EventEmitter(); + + /** + * clear: function() + * Event emitted after the clear icon has been clicked. + */ + @Output('clear') onClear: EventEmitter = new EventEmitter(); + + /** + * blur: function() + * Event emitted after the blur event has been called in underlying input. + */ + @Output('blur') onBlur: EventEmitter = new EventEmitter(); + + ngOnInit(): void { + this.searchTermControl.valueChanges + .debounceTime(this.debounce) + .subscribe((value: string) => { + this._searchTermChanged(value); + }); + } + + /** + * Method to focus to underlying input. + */ + focus(): void { + this._input.focus(); + } + + handleBlur(): void { + this.onBlur.emit(undefined); + } + + handleSearch(event: Event): void { + event.stopPropagation(); + this.onSearch.emit(this.searchTermControl.value); + } + + clearSearch(): void { + this.searchTermControl.setValue(''); + this.onClear.emit(undefined); + } + + private _searchTermChanged(value: string): void { + this.onSearchDebounce.emit(value); + } + +} diff --git a/src/platform/search/search.module.ts b/src/platform/search/search.module.ts new file mode 100644 index 0000000000..93349d5b4b --- /dev/null +++ b/src/platform/search/search.module.ts @@ -0,0 +1,33 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { MaterialModule } from '@angular/material'; + +import { TdSearchInputComponent } from './search-input/search-input.component'; +import { TdSearchBoxComponent } from './search-box/search-box.component'; + +@NgModule({ + imports: [ + ReactiveFormsModule, + CommonModule, + MaterialModule.forRoot(), + ], + declarations: [ + TdSearchInputComponent, + TdSearchBoxComponent, + ], + exports: [ + TdSearchInputComponent, + TdSearchBoxComponent, + ], +}) +export class CovalentSearchModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: CovalentSearchModule, + providers: [ ], + }; + } +}