diff --git a/CHANGELOG.md b/CHANGELOG.md index 56215aa2a22..127d23247d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,16 @@ All notable changes for each version of this project will be documented in this ### New Features +- `IgxActionStrip` component added. + - Provides a template area for one or more actions. In its simplest form the Action Strip + is an overlay of any container and shows additional content over that container. + + ```html + + + + ``` + - `igxSplitter` component added. - Allows rendering a vertical or horizontal splitter with multiple splitter panes with templatable content. Panes can be resized or collapsed/expanded via the UI. Splitter orientation is defined via the `type` input. diff --git a/projects/igniteui-angular/src/lib/action-strip/README.md b/projects/igniteui-angular/src/lib/action-strip/README.md new file mode 100644 index 00000000000..68fbe1aa475 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/README.md @@ -0,0 +1,78 @@ +# igx-action-strip + +The **igx-action-strip** provides a template area for one or more actions. +In its simplest form the Action Strip is an overlay of any container and shows additional content over that container. +A walk-through of how to get started can be found [here](https://www.infragistics.com/products/ignite-ui-angular/angular/components/action_strip.html) + +# Usage +The Action Strip can be initialized in any HTML element that can contain elements. This parent element should be with a relative position as the action strip is trying to overlay it. Interactions with the parent and its content are available while the action strip is shown. +```html + + + +``` + +# Grid Action Components +Action strip provides functionality and UI for IgxGrid. All that can be utilized with grid action components. These components inherit `IgxGridActionsBaseDirective` and when creating a custom grid action component, this component should also inherit `IgxGridActionsBaseDirective`. + +```html + + + + +``` + +# IgxActionStripMenuItem + +The Action Strip can show items as menu. This is achieved with `igxActionStripMenuItem` directive applied to its content. Action strip will render three-dot button that toggles a drop down. And the content will be those items that are marked with `igxActionStripMenuItem` directive. + +```html + + Copy + Paste + Edit + +``` +# API Summary + +## Inputs +`IgxActionStripComponent` + + | Name | Description | Type | Default value | + |-----------------|---------------------------------------------------|-----------------------------|---------------| + | hidden | An @Input property that sets the visibility of the Action Strip. | boolean | `false` | + | context | Sets the context of an action strip. The context should be an instance of a @Component, that has element property. This element will be the placeholder of the action strip. | any | | + +`IgxGridActionsBaseDirective` ( `IgxGridPinningActionsComponent`, `IgxGridEditingActionsComponent`) + + | Name | Description | Type | Default value | + |-----------------|---------------------------------------------------|-----------------------------|---------------| + | grid | Set an instance of the grid for which to display the actions. | any | | + | context | Sets the context of an action strip. The context is expected to be grid cell or grid row | any | | + +## Outputs +|Name|Description|Cancelable|Parameters| +|--|--|--|--| +| onMenuOpening | Emitted before the menu is opened | true | | +| onMenuOpened | Emitted after the menu is opened | false | | + +## Methods + +`IgxActionStripComponent` + + | Name | Description | Return type | Parameters | + |----------|----------------------------|---------------------------------------------------|----------------------| + | show | Showing the Action Strip and appending it the specified context element. | void | context | + | hide | Hiding the Action Strip and removing it from its current context element. | void | | + +`IgxGridPinningActionsComponent` + | Name | Description | Return type | Parameters | + |----------|----------------------------|---------------------------------------------------|----------------------| + | pin | Pin the row according to the context. | void | | + | unpin | Unpin the row according to the context. | void | | + +`IgxGridPinningActionsComponent` + | Name | Description | Return type | Parameters | + |----------|----------------------------|---------------------------------------------------|----------------------| + | startEdit | Enter row or cell edit mode depending the grid `rowEdibable` option | void | | + | deleteRow | Delete a row according to the context | void | | \ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/action-strip/action-strip.component.html b/projects/igniteui-angular/src/lib/action-strip/action-strip.component.html new file mode 100644 index 00000000000..6fef5a08059 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/action-strip.component.html @@ -0,0 +1,17 @@ +
+ + + + + +
+ +
+
+
+
+
diff --git a/projects/igniteui-angular/src/lib/action-strip/action-strip.component.spec.ts b/projects/igniteui-angular/src/lib/action-strip/action-strip.component.spec.ts new file mode 100644 index 00000000000..d7555560cb7 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/action-strip.component.spec.ts @@ -0,0 +1,251 @@ +import { IgxActionStripComponent } from './action-strip.component'; +import { Component, ViewChild, ElementRef, ViewContainerRef } from '@angular/core'; +import { configureTestSuite } from '../test-utils/configure-suite'; +import { TestBed, async } from '@angular/core/testing'; +import { IgxIconModule } from '../icon'; +import { By } from '@angular/platform-browser'; +import { UIInteractions, wait } from '../test-utils/ui-interactions.spec'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { IgxToggleModule } from '../directives/toggle/toggle.directive'; +import { IgxActionStripModule } from './action-strip.module'; + +const ACTION_STRIP_CONTAINER_CSS = 'igx-action-strip__actions'; +const DROP_DOWN_LIST = 'igx-drop-down__list'; + +describe('igxActionStrip', () => { + let fixture; + let actionStrip: IgxActionStripComponent; + let actionStripElement: ElementRef; + let parentContainer: ElementRef; + let innerContainer: ViewContainerRef; + + describe('Unit tests: ', () => { + const mockViewContainerRef = jasmine.createSpyObj('ViewContainerRef', ['element']); + const mockRenderer2 = jasmine.createSpyObj('Renderer2', ['appendChild', 'removeChild']); + const mockContext = jasmine.createSpyObj('context', ['element']); + const mockDisplayDensity = jasmine.createSpyObj('IDisplayDensityOptions', ['displayDensity']); + + it('should properly get/set hidden', () => { + actionStrip = new IgxActionStripComponent(mockViewContainerRef, mockRenderer2, mockDisplayDensity); + expect(actionStrip.hidden).toBeFalsy(); + actionStrip.hidden = true; + expect(actionStrip.hidden).toBeTruthy(); + }); + + it('should properly show and hide using API', () => { + actionStrip = new IgxActionStripComponent(mockViewContainerRef, mockRenderer2, mockDisplayDensity); + actionStrip.show(mockContext); + expect(actionStrip.hidden).toBeFalsy(); + expect(actionStrip.context).toBe(mockContext); + actionStrip.hide(); + expect(actionStrip.hidden).toBeTruthy(); + }); + }); + + describe('Initialization and rendering tests: ', () => { + configureTestSuite(); + beforeAll(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxActionStripTestingComponent + ], + imports: [ + IgxActionStripModule, + IgxIconModule + ] + }).compileComponents(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(IgxActionStripTestingComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + actionStripElement = fixture.componentInstance.actionStripElement; + parentContainer = fixture.componentInstance.parentContainer; + innerContainer = fixture.componentInstance.innerContainer; + }); + it('should be overlapping its parent container when no context is applied', () => { + const parentBoundingRect = parentContainer.nativeElement.getBoundingClientRect(); + const actionStripBoundingRect = actionStripElement.nativeElement.getBoundingClientRect(); + expect(parentBoundingRect.top).toBe(actionStripBoundingRect.top); + expect(parentBoundingRect.bottom).toBe(actionStripBoundingRect.bottom); + expect(parentBoundingRect.left).toBe(actionStripBoundingRect.left); + expect(parentBoundingRect.right).toBe(actionStripBoundingRect.right); + }); + + it('should be overlapping context.element when context is applied', () => { + actionStrip.show(innerContainer); + fixture.detectChanges(); + const innerBoundingRect = innerContainer.element.nativeElement.getBoundingClientRect(); + const actionStripBoundingRect = actionStripElement.nativeElement.getBoundingClientRect(); + expect(innerBoundingRect.top).toBe(actionStripBoundingRect.top); + expect(innerBoundingRect.bottom).toBe(actionStripBoundingRect.bottom); + expect(innerBoundingRect.left).toBe(actionStripBoundingRect.left); + expect(innerBoundingRect.right).toBe(actionStripBoundingRect.right); + }); + + it('should allow interacting with the content elements', () => { + const asIcon = fixture.debugElement.query(By.css('.asIcon')); + asIcon.triggerEventHandler('click', new Event('click')); + fixture.detectChanges(); + expect(fixture.componentInstance.flag).toBeTruthy(); + }); + + it('should not display the action strip when setting it hidden', () => { + actionStrip.hidden = true; + fixture.detectChanges(); + const asQuery = fixture.debugElement.query(By.css('igx-action-strip')); + expect(asQuery.nativeElement.style.display).toBe('none'); + }); + }); + + describe('render content as menu', () => { + configureTestSuite(); + beforeAll(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxActionStripMenuTestingComponent, + IgxActionStripCombinedMenuTestingComponent + ], + imports: [ + IgxActionStripModule, + IgxIconModule, + NoopAnimationsModule, + IgxToggleModule + ] + }).compileComponents(); + })); + + it('should render tree-dot button which toggles the content as menu', () => { + fixture = TestBed.createComponent(IgxActionStripMenuTestingComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + const actionStripContainer = fixture.debugElement.query(By.css(`.${ACTION_STRIP_CONTAINER_CSS}`)); + // there should be one rendered child and one hidden dropdown + expect(actionStripContainer.nativeElement.children.length).toBe(2); + let dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`)); + expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true'); + const icon = fixture.debugElement.query(By.css(`igx-icon`)); + icon.parent.triggerEventHandler('click', new Event('click')); + fixture.detectChanges(); + dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`)); + expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('false'); + const dropDownItems = dropDownList.queryAll(By.css('igx-drop-down-item')); + expect(dropDownItems.length).toBe(3); + }); + + it('should emit onMenuOpen/onMenuOpening when toggling the menu', () => { + pending('implementation'); + }); + + it('should allow combining content outside and inside the menu', () => { + fixture = TestBed.createComponent(IgxActionStripCombinedMenuTestingComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + const actionStripContainer = fixture.debugElement.query(By.css(`.${ACTION_STRIP_CONTAINER_CSS}`)); + // there should be one rendered child and one hidden dropdown and one additional icon + expect(actionStripContainer.nativeElement.children.length).toBe(3); + let dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`)); + expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true'); + const icon = fixture.debugElement.query(By.css(`igx-icon`)); + icon.parent.triggerEventHandler('click', new Event('click')); + fixture.detectChanges(); + dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`)); + expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('false'); + const dropDownItems = dropDownList.queryAll(By.css('igx-drop-down-item')); + expect(dropDownItems.length).toBe(2); + }); + + it('should close the menu when hiding action strip', async() => { + fixture = TestBed.createComponent(IgxActionStripCombinedMenuTestingComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + // there should be one rendered child and one hidden dropdown and one additional icon + const icon = fixture.debugElement.query(By.css(`igx-icon`)); + icon.parent.triggerEventHandler('click', new Event('click')); + fixture.detectChanges(); + let dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`)); + expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('false'); + actionStrip.hide(); + await wait(); + fixture.detectChanges(); + dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`)); + expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true'); + }); + }); +}); + +@Component({ + template: ` +
+
+

+ Lorem ipsum dolor sit +

+
+ + alarm + +
+` +}) +class IgxActionStripTestingComponent { + @ViewChild('actionStrip', { read: IgxActionStripComponent, static: true }) + public actionStrip: IgxActionStripComponent; + + @ViewChild('actionStrip', { read: ElementRef, static: true }) + public actionStripElement: ElementRef; + + @ViewChild('parent', { static: true }) + public parentContainer: ElementRef; + + @ViewChild('inner', { read: ViewContainerRef, static: true }) + public innerContainer: ViewContainerRef; + + public flag = false; + + onIconClick() { + this.flag = true; + } +} + +@Component({ + template: ` +
+
+

+ Lorem ipsum dolor sit +

+
+ + Mark + Favorite + Download + +
+ ` +}) +class IgxActionStripMenuTestingComponent { + @ViewChild('actionStrip', { read: IgxActionStripComponent, static: true }) + public actionStrip: IgxActionStripComponent; +} + +@Component({ + template: ` +
+
+

+ Lorem ipsum dolor sit +

+
+ + Mark + Favorite + Download + +
+ ` +}) +class IgxActionStripCombinedMenuTestingComponent { + @ViewChild('actionStrip', { read: IgxActionStripComponent, static: true }) + public actionStrip: IgxActionStripComponent; +} diff --git a/projects/igniteui-angular/src/lib/action-strip/action-strip.component.ts b/projects/igniteui-angular/src/lib/action-strip/action-strip.component.ts new file mode 100644 index 00000000000..6d16977d709 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/action-strip.component.ts @@ -0,0 +1,200 @@ +import { + Component, + Directive, + HostBinding, + Input, + Renderer2, + ViewContainerRef, + Optional, + Inject, + ContentChildren, + QueryList, + ViewChild, + TemplateRef +} from '@angular/core'; +import { DisplayDensityBase, DisplayDensityToken, IDisplayDensityOptions } from '../core/density'; +import { IgxDropDownComponent } from '../drop-down'; +import { CloseScrollStrategy, OverlaySettings } from '../services'; + +@Directive({ + selector: '[igxActionStripMenuItem]' +}) +export class IgxActionStripMenuItemDirective { + constructor( + public templateRef: TemplateRef + ) { } +} + +/** + * Action Strip provides templatable area for one or more actions. + * + * @igxModule IgxActionStripModule + * + * @igxTheme igx-action-strip-theme + * + * @igxKeywords action, strip, actionStrip, pinning, editing + * + * @igxGroup Data Entry & Display + * + * @remarks + * The Ignite UI Action Strip is a container, overlaying its parent container, + * and displaying action buttons with action applicable to the parent component the strip is instantiated or shown for. + * + * @example + * ```html + * + * + * + */ +@Component({ + selector: 'igx-action-strip', + templateUrl: 'action-strip.component.html' +}) + +export class IgxActionStripComponent extends DisplayDensityBase { + constructor( + private _viewContainer: ViewContainerRef, + private renderer: Renderer2, + @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { + super(_displayDensityOptions); + } + + /** + * Getter for the 'display' property of the current `IgxActionStrip` + * @hidden + * @internal + */ + @HostBinding('style.display') + get display(): string { + return this._hidden ? 'none' : 'flex'; + } + + private _hidden = false; + + /** + * An @Input property that set the visibility of the Action Strip. + * Could be used to set if the Action Strip will be initially hidden. + * @example + * ```html + * + * ``` + */ + @Input() + public set hidden(value) { + this._hidden = value; + } + + public get hidden() { + return this._hidden; + } + + /** + * Host `class.igx-action-strip` binding. + * @hidden + * @internal + */ + @Input('class') + hostClass: string; + + /** + * Host `attr.class` binding. + * @hidden + * @internal + */ + @HostBinding('attr.class') + get hostClasses(): string { + const classes = [this.getComponentDensityClass('igx-action-strip')]; + // The custom classes should be at the end. + if (!classes.includes('igx-action-strip')) { + classes.push('igx-action-strip'); + } + classes.push(this.hostClass); + return classes.join(' '); + } + + /** + * Sets the context of an action strip. + * The context should be an instance of a @Component, that has element property. + * This element will be the placeholder of the action strip. + * @example + * ```html + * + * ``` + */ + @Input() + public context: any; + /** + * Menu Items ContentChildren inside the Action Strip + * @hidden + * @internal + */ + @ContentChildren(IgxActionStripMenuItemDirective) + public menuItems: QueryList; + + /** + * Reference to the menu + * @hidden + * @internal + */ + @ViewChild('dropdown') + private menu: IgxDropDownComponent; + + /** + * Showing the Action Strip and appending it the specified context element. + * @param context + * @example + * ```typescript + * this.actionStrip.show(row); + * ``` + */ + public show(context?: any): void { + this.hidden = false; + if (!context) { + return; + } + // when shown for different context make sure the menu won't stay opened + if (this.context !== context) { + this.closeMenu(); + } + this.context = context; + if (this.context && this.context.element) { + this.renderer.appendChild(context.element.nativeElement, this._viewContainer.element.nativeElement); + } + } + + /** + * Hiding the Action Strip and removing it from its current context element. + * @example + * ```typescript + * this.actionStrip.hide(); + * ``` + */ + public hide(): void { + this.hidden = true; + this.closeMenu(); + if (this.context && this.context.element) { + this.renderer.removeChild(this.context.element.nativeElement, this._viewContainer.element.nativeElement); + } + } + + /** + * Getter for menu overlay settings + * @hidden + * @internal + */ + get menuOverlaySettings (): OverlaySettings { + return { scrollStrategy: new CloseScrollStrategy() }; + } + + /** + * Close the menu if opened + * @hidden + * @internal + */ + private closeMenu(): void { + if (this.menu && !this.menu.collapsed) { + this.menu.close(); + } + } +} + diff --git a/projects/igniteui-angular/src/lib/action-strip/action-strip.module.ts b/projects/igniteui-angular/src/lib/action-strip/action-strip.module.ts new file mode 100644 index 00000000000..478962d5482 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/action-strip.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { IgxActionStripComponent, IgxActionStripMenuItemDirective } from './action-strip.component'; +import { IgxGridPinningActionsComponent } from './grid-actions/grid-pinning-actions.component'; +import { IgxGridEditingActionsComponent } from './grid-actions/grid-editing-actions.component'; +import { IgxGridActionsBaseDirective } from './grid-actions/grid-actions-base.directive'; +import { CommonModule } from '@angular/common'; +import { IgxDropDownModule } from '../drop-down/index'; +import { IgxToggleModule } from '../directives/toggle/toggle.directive'; +import { IgxButtonModule } from '../directives/button/button.directive'; +import { IgxIconModule } from '../icon/index'; +import { IgxRippleModule } from '../directives/ripple/ripple.directive'; + +/** + * @hidden + */ +@NgModule({ + declarations: [ + IgxActionStripComponent, + IgxActionStripMenuItemDirective, + IgxGridPinningActionsComponent, + IgxGridEditingActionsComponent, + IgxGridActionsBaseDirective + ], + entryComponents: [ + ], + exports: [ + IgxActionStripComponent, + IgxActionStripMenuItemDirective, + IgxGridPinningActionsComponent, + IgxGridEditingActionsComponent, + IgxGridActionsBaseDirective + ], + imports: [CommonModule, IgxDropDownModule, IgxToggleModule, IgxButtonModule, IgxIconModule, IgxRippleModule] +}) +export class IgxActionStripModule { } diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-actions-base.directive.ts b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-actions-base.directive.ts new file mode 100644 index 00000000000..ae8bc91924d --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-actions-base.directive.ts @@ -0,0 +1,29 @@ +import { Directive, Inject } from '@angular/core'; +import { IgxActionStripComponent } from '../action-strip.component'; +import { IgxRowDirective } from '../../grids'; + +@Directive({ + selector: '[igxGridActionsBase]' +}) +export class IgxGridActionsBaseDirective { + constructor(@Inject(IgxActionStripComponent) protected strip: IgxActionStripComponent) { } + + /** + * Getter to be used in template + * @hidden + * @internal + */ + get isRowContext(): boolean { + return this.isRow(this.strip.context); + } + + /** + * Check if the param is a row from a grid + * @hidden + * @internal + * @param context + */ + protected isRow(context): context is IgxRowDirective { + return context && context instanceof IgxRowDirective; + } +} diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.html b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.html new file mode 100644 index 00000000000..23a4c1939b4 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.html @@ -0,0 +1,8 @@ + + + + diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.spec.ts b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.spec.ts new file mode 100644 index 00000000000..a025c326835 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.spec.ts @@ -0,0 +1,174 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; +import { IgxActionStripComponent } from '../action-strip.component'; +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { TestBed, async } from '@angular/core/testing'; +import { IgxIconModule } from '../../icon'; +import { IgxGridModule, IgxGridComponent } from '../../grids/grid'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { By } from '@angular/platform-browser'; +import { IgxActionStripModule } from '../action-strip.module'; + + +describe('igxGridEditingActions #grid ', () => { + let fixture; + let actionStrip: IgxActionStripComponent; + let grid: IgxGridComponent; + configureTestSuite(); + beforeAll(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxActionStripTestingComponent, + IgxActionStripPinEditComponent + ], + imports: [ + NoopAnimationsModule, + IgxActionStripModule, + IgxGridModule, + IgxIconModule + ] + }).compileComponents(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(IgxActionStripTestingComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + grid = fixture.componentInstance.grid; + }); + + it('should allow editing and deleting row', () => { + let editIcon, deleteIcon; + actionStrip.show(grid.rowList.first); + fixture.detectChanges(); + editIcon = fixture.debugElement.queryAll(By.css(`igx-grid-editing-actions igx-icon`))[0]; + expect(editIcon.nativeElement.innerText).toBe('edit'); + editIcon.parent.triggerEventHandler('click', new Event('click')); + fixture.detectChanges(); + expect(grid.rowInEditMode).not.toBeNull(); + expect(grid.rowList.first.inEditMode).toBe(true); + + expect(grid.rowList.first.rowData['ID']).toBe('ALFKI'); + const dataLenght = grid.dataLength; + actionStrip.show(grid.rowList.first); + fixture.detectChanges(); + deleteIcon = fixture.debugElement.queryAll(By.css(`igx-grid-editing-actions igx-icon`))[1]; + expect(deleteIcon.nativeElement.innerText).toBe('delete'); + deleteIcon.parent.triggerEventHandler('click', new Event('click')); + actionStrip.hide(); + fixture.detectChanges(); + expect(grid.rowList.first.rowData['ID']).toBe('ANATR'); + expect(dataLenght - 1).toBe(grid.dataLength); + }); + + describe('integration with pinning actions ', () => { + beforeEach(() => { + fixture = TestBed.createComponent(IgxActionStripPinEditComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + grid = fixture.componentInstance.grid; + }); + it('should disable editing actions on disabled rows', () => { + grid.rowList.first.pin(); + fixture.detectChanges(); + actionStrip.show(grid.rowList.toArray()[1]); + fixture.detectChanges(); + const editingIcons = fixture.debugElement.queryAll(By.css(`igx-grid-editing-actions button`)); + const pinningIcons = fixture.debugElement.queryAll(By.css(`igx-grid-pinning-actions button`)); + expect(editingIcons.length).toBe(2); + expect(editingIcons[0].nativeElement.className.indexOf('igx-button--disabled') !== -1).toBeTruthy(); + expect(editingIcons[1].nativeElement.className.indexOf('igx-button--disabled') !== -1).toBeTruthy(); + expect(pinningIcons.length).toBe(1); + expect(pinningIcons[0].nativeElement.className.indexOf('igx-button--disabled') === -1).toBeTruthy(); + }); + }); +}); + +@Component({ + template: ` + + + + + + + + +` +}) +class IgxActionStripTestingComponent implements OnInit { + @ViewChild('actionStrip', { read: IgxActionStripComponent, static: true }) + public actionStrip: IgxActionStripComponent; + + @ViewChild('grid', { read: IgxGridComponent, static: true }) + public grid: IgxGridComponent; + + data: any[]; + columns: any[]; + + ngOnInit() { + + this.columns = [ + { field: 'ID', width: '200px', hidden: false }, + { field: 'CompanyName', width: '200px' }, + { field: 'ContactName', width: '200px', pinned: false }, + { field: 'ContactTitle', width: '300px', pinned: false }, + { field: 'Address', width: '250px' }, + { field: 'City', width: '200px' }, + { field: 'Region', width: '300px' }, + { field: 'PostalCode', width: '150px' }, + { field: 'Phone', width: '200px' }, + { field: 'Fax', width: '200px' } + ]; + + this.data = [ + // tslint:disable:max-line-length + { 'ID': 'ALFKI', 'CompanyName': 'Alfreds Futterkiste', 'ContactName': 'Maria Anders', 'ContactTitle': 'Sales Representative', 'Address': 'Obere Str. 57', 'City': 'Berlin', 'Region': null, 'PostalCode': '12209', 'Country': 'Germany', 'Phone': '030-0074321', 'Fax': '030-0076545' }, + { 'ID': 'ANATR', 'CompanyName': 'Ana Trujillo Emparedados y helados', 'ContactName': 'Ana Trujillo', 'ContactTitle': 'Owner', 'Address': 'Avda. de la Constitución 2222', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05021', 'Country': 'Mexico', 'Phone': '(5) 555-4729', 'Fax': '(5) 555-3745' }, + { 'ID': 'ANTON', 'CompanyName': 'Antonio Moreno Taquería', 'ContactName': 'Antonio Moreno', 'ContactTitle': 'Owner', 'Address': 'Mataderos 2312', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05023', 'Country': 'Mexico', 'Phone': '(5) 555-3932', 'Fax': null }, + { 'ID': 'AROUT', 'CompanyName': 'Around the Horn', 'ContactName': 'Thomas Hardy', 'ContactTitle': 'Sales Representative', 'Address': '120 Hanover Sq.', 'City': 'London', 'Region': null, 'PostalCode': 'WA1 1DP', 'Country': 'UK', 'Phone': '(171) 555-7788', 'Fax': '(171) 555-6750' }, + { 'ID': 'BERGS', 'CompanyName': 'Berglunds snabbköp', 'ContactName': 'Christina Berglund', 'ContactTitle': 'Order Administrator', 'Address': 'Berguvsvägen 8', 'City': 'Luleå', 'Region': null, 'PostalCode': 'S-958 22', 'Country': 'Sweden', 'Phone': '0921-12 34 65', 'Fax': '0921-12 34 67' }, + { 'ID': 'BLAUS', 'CompanyName': 'Blauer See Delikatessen', 'ContactName': 'Hanna Moos', 'ContactTitle': 'Sales Representative', 'Address': 'Forsterstr. 57', 'City': 'Mannheim', 'Region': null, 'PostalCode': '68306', 'Country': 'Germany', 'Phone': '0621-08460', 'Fax': '0621-08924' }, + { 'ID': 'BLONP', 'CompanyName': 'Blondesddsl père et fils', 'ContactName': 'Frédérique Citeaux', 'ContactTitle': 'Marketing Manager', 'Address': '24, place Kléber', 'City': 'Strasbourg', 'Region': null, 'PostalCode': '67000', 'Country': 'France', 'Phone': '88.60.15.31', 'Fax': '88.60.15.32' }, + { 'ID': 'BOLID', 'CompanyName': 'Bólido Comidas preparadas', 'ContactName': 'Martín Sommer', 'ContactTitle': 'Owner', 'Address': 'C/ Araquil, 67', 'City': 'Madrid', 'Region': null, 'PostalCode': '28023', 'Country': 'Spain', 'Phone': '(91) 555 22 82', 'Fax': '(91) 555 91 99' }, + { 'ID': 'BONAP', 'CompanyName': 'Bon app\'', 'ContactName': 'Laurence Lebihan', 'ContactTitle': 'Owner', 'Address': '12, rue des Bouchers', 'City': 'Marseille', 'Region': null, 'PostalCode': '13008', 'Country': 'France', 'Phone': '91.24.45.40', 'Fax': '91.24.45.41' }, + { 'ID': 'BOTTM', 'CompanyName': 'Bottom-Dollar Markets', 'ContactName': 'Elizabeth Lincoln', 'ContactTitle': 'Accounting Manager', 'Address': '23 Tsawassen Blvd.', 'City': 'Tsawassen', 'Region': 'BC', 'PostalCode': 'T2F 8M4', 'Country': 'Canada', 'Phone': '(604) 555-4729', 'Fax': '(604) 555-3745' }, + { 'ID': 'BSBEV', 'CompanyName': 'B\'s Beverages', 'ContactName': 'Victoria Ashworth', 'ContactTitle': 'Sales Representative', 'Address': 'Fauntleroy Circus', 'City': 'London', 'Region': null, 'PostalCode': 'EC2 5NT', 'Country': 'UK', 'Phone': '(171) 555-1212', 'Fax': null }, + { 'ID': 'CACTU', 'CompanyName': 'Cactus Comidas para llevar', 'ContactName': 'Patricio Simpson', 'ContactTitle': 'Sales Agent', 'Address': 'Cerrito 333', 'City': 'Buenos Aires', 'Region': null, 'PostalCode': '1010', 'Country': 'Argentina', 'Phone': '(1) 135-5555', 'Fax': '(1) 135-4892' }, + { 'ID': 'CENTC', 'CompanyName': 'Centro comercial Moctezuma', 'ContactName': 'Francisco Chang', 'ContactTitle': 'Marketing Manager', 'Address': 'Sierras de Granada 9993', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05022', 'Country': 'Mexico', 'Phone': '(5) 555-3392', 'Fax': '(5) 555-7293' }, + { 'ID': 'CHOPS', 'CompanyName': 'Chop-suey Chinese', 'ContactName': 'Yang Wang', 'ContactTitle': 'Owner', 'Address': 'Hauptstr. 29', 'City': 'Bern', 'Region': null, 'PostalCode': '3012', 'Country': 'Switzerland', 'Phone': '0452-076545', 'Fax': null }, + { 'ID': 'COMMI', 'CompanyName': 'Comércio Mineiro', 'ContactName': 'Pedro Afonso', 'ContactTitle': 'Sales Associate', 'Address': 'Av. dos Lusíadas, 23', 'City': 'Sao Paulo', 'Region': 'SP', 'PostalCode': '05432-043', 'Country': 'Brazil', 'Phone': '(11) 555-7647', 'Fax': null }, + { 'ID': 'CONSH', 'CompanyName': 'Consolidated Holdings', 'ContactName': 'Elizabeth Brown', 'ContactTitle': 'Sales Representative', 'Address': 'Berkeley Gardens 12 Brewery', 'City': 'London', 'Region': null, 'PostalCode': 'WX1 6LT', 'Country': 'UK', 'Phone': '(171) 555-2282', 'Fax': '(171) 555-9199' }, + { 'ID': 'DRACD', 'CompanyName': 'Drachenblut Delikatessen', 'ContactName': 'Sven Ottlieb', 'ContactTitle': 'Order Administrator', 'Address': 'Walserweg 21', 'City': 'Aachen', 'Region': null, 'PostalCode': '52066', 'Country': 'Germany', 'Phone': '0241-039123', 'Fax': '0241-059428' }, + { 'ID': 'DUMON', 'CompanyName': 'Du monde entier', 'ContactName': 'Janine Labrune', 'ContactTitle': 'Owner', 'Address': '67, rue des Cinquante Otages', 'City': 'Nantes', 'Region': null, 'PostalCode': '44000', 'Country': 'France', 'Phone': '40.67.88.88', 'Fax': '40.67.89.89' }, + { 'ID': 'EASTC', 'CompanyName': 'Eastern Connection', 'ContactName': 'Ann Devon', 'ContactTitle': 'Sales Agent', 'Address': '35 King George', 'City': 'London', 'Region': null, 'PostalCode': 'WX3 6FW', 'Country': 'UK', 'Phone': '(171) 555-0297', 'Fax': '(171) 555-3373' }, + { 'ID': 'ERNSH', 'CompanyName': 'Ernst Handel', 'ContactName': 'Roland Mendel', 'ContactTitle': 'Sales Manager', 'Address': 'Kirchgasse 6', 'City': 'Graz', 'Region': null, 'PostalCode': '8010', 'Country': 'Austria', 'Phone': '7675-3425', 'Fax': '7675-3426' }, + { 'ID': 'FAMIA', 'CompanyName': 'Familia Arquibaldo', 'ContactName': 'Aria Cruz', 'ContactTitle': 'Marketing Assistant', 'Address': 'Rua Orós, 92', 'City': 'Sao Paulo', 'Region': 'SP', 'PostalCode': '05442-030', 'Country': 'Brazil', 'Phone': '(11) 555-9857', 'Fax': null }, + { 'ID': 'FISSA', 'CompanyName': 'FISSA Fabrica Inter. Salchichas S.A.', 'ContactName': 'Diego Roel', 'ContactTitle': 'Accounting Manager', 'Address': 'C/ Moralzarzal, 86', 'City': 'Madrid', 'Region': null, 'PostalCode': '28034', 'Country': 'Spain', 'Phone': '(91) 555 94 44', 'Fax': '(91) 555 55 93' }, + { 'ID': 'FOLIG', 'CompanyName': 'Folies gourmandes', 'ContactName': 'Martine Rancé', 'ContactTitle': 'Assistant Sales Agent', 'Address': '184, chaussée de Tournai', 'City': 'Lille', 'Region': null, 'PostalCode': '59000', 'Country': 'France', 'Phone': '20.16.10.16', 'Fax': '20.16.10.17' }, + { 'ID': 'FOLKO', 'CompanyName': 'Folk och fä HB', 'ContactName': 'Maria Larsson', 'ContactTitle': 'Owner', 'Address': 'Åkergatan 24', 'City': 'Bräcke', 'Region': null, 'PostalCode': 'S-844 67', 'Country': 'Sweden', 'Phone': '0695-34 67 21', 'Fax': null }, + { 'ID': 'FRANK', 'CompanyName': 'Frankenversand', 'ContactName': 'Peter Franken', 'ContactTitle': 'Marketing Manager', 'Address': 'Berliner Platz 43', 'City': 'München', 'Region': null, 'PostalCode': '80805', 'Country': 'Germany', 'Phone': '089-0877310', 'Fax': '089-0877451' }, + { 'ID': 'FRANR', 'CompanyName': 'France restauration', 'ContactName': 'Carine Schmitt', 'ContactTitle': 'Marketing Manager', 'Address': '54, rue Royale', 'City': 'Nantes', 'Region': null, 'PostalCode': '44000', 'Country': 'France', 'Phone': '40.32.21.21', 'Fax': '40.32.21.20' }, + { 'ID': 'FRANS', 'CompanyName': 'Franchi S.p.A.', 'ContactName': 'Paolo Accorti', 'ContactTitle': 'Sales Representative', 'Address': 'Via Monte Bianco 34', 'City': 'Torino', 'Region': null, 'PostalCode': '10100', 'Country': 'Italy', 'Phone': '011-4988260', 'Fax': '011-4988261' } + ]; + // tslint:enable:max-line-length + } +} + +@Component({ + template: ` + + + + + + + + + +` +}) +class IgxActionStripPinEditComponent extends IgxActionStripTestingComponent { +} diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.ts b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.ts new file mode 100644 index 00000000000..fb44913dfcd --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.ts @@ -0,0 +1,74 @@ +import { Component, HostBinding } from '@angular/core'; +import { IgxGridActionsBaseDirective } from './grid-actions-base.directive'; + +@Component({ + selector: 'igx-grid-editing-actions', + templateUrl: 'grid-editing-actions.component.html', + providers: [{ provide: IgxGridActionsBaseDirective, useExisting: IgxGridEditingActionsComponent }] +}) + +export class IgxGridEditingActionsComponent extends IgxGridActionsBaseDirective { + /** + * Host `class.igx-action-strip` binding. + * @hidden + * @internal + */ + @HostBinding('class.igx-action-strip__editing-actions') + public cssClass = 'igx-action-strip__editing-actions'; + + /** + * Enter row or cell edit mode depending the grid rowEditable option + * @example + * ```typescript + * this.gridEditingActions.startEdit(); + * ``` + */ + public startEdit(event?): void { + if (event) { + event.stopPropagation(); + } + if (!this.isRow(this.strip.context)) { + return; + } + const row = this.strip.context; + const firstEditable = row.cells.filter(cell => cell.editable)[0]; + const grid = row.grid; + // be sure row is in view + if (grid.rowList.filter(r => r === row).length !== 0) { + grid.crudService.begin(firstEditable); + } + this.strip.hide(); + } + + /** + * Delete a row according to the context + * @example + * ```typescript + * this.gridEditingActions.deleteRow(); + * ``` + */ + public deleteRow(event?): void { + if (event) { + event.stopPropagation(); + } + if (!this.isRow(this.strip.context)) { + return; + } + const context = this.strip.context; + const grid = context.grid; + grid.deleteRow(context.rowID); + this.strip.hide(); + } + + /** + * Getter if the row is disabled + * @hidden + * @internal + */ + get disabled(): boolean { + if (!this.isRow(this.strip.context)) { + return; + } + return this.strip.context.disabled; + } +} diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.html b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.html new file mode 100644 index 00000000000..1e876aff489 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.html @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.spec.ts b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.spec.ts new file mode 100644 index 00000000000..72ff9ed1052 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.spec.ts @@ -0,0 +1,136 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; +import { IgxActionStripComponent } from '../action-strip.component'; +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { TestBed, async } from '@angular/core/testing'; +import { IgxIconModule } from '../../icon'; +import { IgxGridModule, IgxGridComponent } from '../../grids/grid'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { By } from '@angular/platform-browser'; +import { IgxActionStripModule } from '../action-strip.module'; + + +describe('igxGridPinningActions #grid ', () => { + let fixture; + let actionStrip: IgxActionStripComponent; + let grid: IgxGridComponent; + configureTestSuite(); + beforeAll(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxActionStripTestingComponent + ], + imports: [ + NoopAnimationsModule, + IgxActionStripModule, + IgxGridModule, + IgxIconModule + ] + }).compileComponents(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(IgxActionStripTestingComponent); + fixture.detectChanges(); + actionStrip = fixture.componentInstance.actionStrip; + grid = fixture.componentInstance.grid; + }); + + it('should allow pinning and unpinning rows in a grid', () => { + let pinIcon, unpinIcon; + actionStrip.show(grid.rowList.first); + fixture.detectChanges(); + pinIcon = fixture.debugElement.query(By.css(`igx-icon[name=pin]`)); + unpinIcon = fixture.debugElement.query(By.css(`igx-icon[name=unpin]`)); + expect(unpinIcon).toBeNull(); + pinIcon.parent.triggerEventHandler('click', new Event('click')); + actionStrip.hide(); + fixture.detectChanges(); + expect(grid.pinnedRows.length).toBe(1); + + actionStrip.show(grid.pinnedRows[0]); + fixture.detectChanges(); + pinIcon = fixture.debugElement.query(By.css(`igx-icon[name=pin]`)); + unpinIcon = fixture.debugElement.query(By.css(`igx-icon[name=unpin]`)); + expect(pinIcon).toBe(null); + unpinIcon.parent.triggerEventHandler('click', new Event('click')); + actionStrip.hide(); + fixture.detectChanges(); + expect(grid.pinnedRows.length).toBe(0); + }); + + it('should allow navigating to disabled row in unpinned area', () => { + pending('implementation'); + }); +}); + +@Component({ + template: ` + + + + + + + + +` +}) +class IgxActionStripTestingComponent implements OnInit { + @ViewChild('actionStrip', { read: IgxActionStripComponent, static: true }) + public actionStrip: IgxActionStripComponent; + + @ViewChild('grid', { read: IgxGridComponent, static: true }) + public grid: IgxGridComponent; + + data: any[]; + columns: any[]; + + ngOnInit() { + + this.columns = [ + { field: 'ID', width: '200px', hidden: false }, + { field: 'CompanyName', width: '200px' }, + { field: 'ContactName', width: '200px', pinned: false }, + { field: 'ContactTitle', width: '300px', pinned: false }, + { field: 'Address', width: '250px' }, + { field: 'City', width: '200px' }, + { field: 'Region', width: '300px' }, + { field: 'PostalCode', width: '150px' }, + { field: 'Phone', width: '200px' }, + { field: 'Fax', width: '200px' } + ]; + + this.data = [ + // tslint:disable:max-line-length + { 'ID': 'ALFKI', 'CompanyName': 'Alfreds Futterkiste', 'ContactName': 'Maria Anders', 'ContactTitle': 'Sales Representative', 'Address': 'Obere Str. 57', 'City': 'Berlin', 'Region': null, 'PostalCode': '12209', 'Country': 'Germany', 'Phone': '030-0074321', 'Fax': '030-0076545' }, + { 'ID': 'ANATR', 'CompanyName': 'Ana Trujillo Emparedados y helados', 'ContactName': 'Ana Trujillo', 'ContactTitle': 'Owner', 'Address': 'Avda. de la Constitución 2222', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05021', 'Country': 'Mexico', 'Phone': '(5) 555-4729', 'Fax': '(5) 555-3745' }, + { 'ID': 'ANTON', 'CompanyName': 'Antonio Moreno Taquería', 'ContactName': 'Antonio Moreno', 'ContactTitle': 'Owner', 'Address': 'Mataderos 2312', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05023', 'Country': 'Mexico', 'Phone': '(5) 555-3932', 'Fax': null }, + { 'ID': 'AROUT', 'CompanyName': 'Around the Horn', 'ContactName': 'Thomas Hardy', 'ContactTitle': 'Sales Representative', 'Address': '120 Hanover Sq.', 'City': 'London', 'Region': null, 'PostalCode': 'WA1 1DP', 'Country': 'UK', 'Phone': '(171) 555-7788', 'Fax': '(171) 555-6750' }, + { 'ID': 'BERGS', 'CompanyName': 'Berglunds snabbköp', 'ContactName': 'Christina Berglund', 'ContactTitle': 'Order Administrator', 'Address': 'Berguvsvägen 8', 'City': 'Luleå', 'Region': null, 'PostalCode': 'S-958 22', 'Country': 'Sweden', 'Phone': '0921-12 34 65', 'Fax': '0921-12 34 67' }, + { 'ID': 'BLAUS', 'CompanyName': 'Blauer See Delikatessen', 'ContactName': 'Hanna Moos', 'ContactTitle': 'Sales Representative', 'Address': 'Forsterstr. 57', 'City': 'Mannheim', 'Region': null, 'PostalCode': '68306', 'Country': 'Germany', 'Phone': '0621-08460', 'Fax': '0621-08924' }, + { 'ID': 'BLONP', 'CompanyName': 'Blondesddsl père et fils', 'ContactName': 'Frédérique Citeaux', 'ContactTitle': 'Marketing Manager', 'Address': '24, place Kléber', 'City': 'Strasbourg', 'Region': null, 'PostalCode': '67000', 'Country': 'France', 'Phone': '88.60.15.31', 'Fax': '88.60.15.32' }, + { 'ID': 'BOLID', 'CompanyName': 'Bólido Comidas preparadas', 'ContactName': 'Martín Sommer', 'ContactTitle': 'Owner', 'Address': 'C/ Araquil, 67', 'City': 'Madrid', 'Region': null, 'PostalCode': '28023', 'Country': 'Spain', 'Phone': '(91) 555 22 82', 'Fax': '(91) 555 91 99' }, + { 'ID': 'BONAP', 'CompanyName': 'Bon app\'', 'ContactName': 'Laurence Lebihan', 'ContactTitle': 'Owner', 'Address': '12, rue des Bouchers', 'City': 'Marseille', 'Region': null, 'PostalCode': '13008', 'Country': 'France', 'Phone': '91.24.45.40', 'Fax': '91.24.45.41' }, + { 'ID': 'BOTTM', 'CompanyName': 'Bottom-Dollar Markets', 'ContactName': 'Elizabeth Lincoln', 'ContactTitle': 'Accounting Manager', 'Address': '23 Tsawassen Blvd.', 'City': 'Tsawassen', 'Region': 'BC', 'PostalCode': 'T2F 8M4', 'Country': 'Canada', 'Phone': '(604) 555-4729', 'Fax': '(604) 555-3745' }, + { 'ID': 'BSBEV', 'CompanyName': 'B\'s Beverages', 'ContactName': 'Victoria Ashworth', 'ContactTitle': 'Sales Representative', 'Address': 'Fauntleroy Circus', 'City': 'London', 'Region': null, 'PostalCode': 'EC2 5NT', 'Country': 'UK', 'Phone': '(171) 555-1212', 'Fax': null }, + { 'ID': 'CACTU', 'CompanyName': 'Cactus Comidas para llevar', 'ContactName': 'Patricio Simpson', 'ContactTitle': 'Sales Agent', 'Address': 'Cerrito 333', 'City': 'Buenos Aires', 'Region': null, 'PostalCode': '1010', 'Country': 'Argentina', 'Phone': '(1) 135-5555', 'Fax': '(1) 135-4892' }, + { 'ID': 'CENTC', 'CompanyName': 'Centro comercial Moctezuma', 'ContactName': 'Francisco Chang', 'ContactTitle': 'Marketing Manager', 'Address': 'Sierras de Granada 9993', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05022', 'Country': 'Mexico', 'Phone': '(5) 555-3392', 'Fax': '(5) 555-7293' }, + { 'ID': 'CHOPS', 'CompanyName': 'Chop-suey Chinese', 'ContactName': 'Yang Wang', 'ContactTitle': 'Owner', 'Address': 'Hauptstr. 29', 'City': 'Bern', 'Region': null, 'PostalCode': '3012', 'Country': 'Switzerland', 'Phone': '0452-076545', 'Fax': null }, + { 'ID': 'COMMI', 'CompanyName': 'Comércio Mineiro', 'ContactName': 'Pedro Afonso', 'ContactTitle': 'Sales Associate', 'Address': 'Av. dos Lusíadas, 23', 'City': 'Sao Paulo', 'Region': 'SP', 'PostalCode': '05432-043', 'Country': 'Brazil', 'Phone': '(11) 555-7647', 'Fax': null }, + { 'ID': 'CONSH', 'CompanyName': 'Consolidated Holdings', 'ContactName': 'Elizabeth Brown', 'ContactTitle': 'Sales Representative', 'Address': 'Berkeley Gardens 12 Brewery', 'City': 'London', 'Region': null, 'PostalCode': 'WX1 6LT', 'Country': 'UK', 'Phone': '(171) 555-2282', 'Fax': '(171) 555-9199' }, + { 'ID': 'DRACD', 'CompanyName': 'Drachenblut Delikatessen', 'ContactName': 'Sven Ottlieb', 'ContactTitle': 'Order Administrator', 'Address': 'Walserweg 21', 'City': 'Aachen', 'Region': null, 'PostalCode': '52066', 'Country': 'Germany', 'Phone': '0241-039123', 'Fax': '0241-059428' }, + { 'ID': 'DUMON', 'CompanyName': 'Du monde entier', 'ContactName': 'Janine Labrune', 'ContactTitle': 'Owner', 'Address': '67, rue des Cinquante Otages', 'City': 'Nantes', 'Region': null, 'PostalCode': '44000', 'Country': 'France', 'Phone': '40.67.88.88', 'Fax': '40.67.89.89' }, + { 'ID': 'EASTC', 'CompanyName': 'Eastern Connection', 'ContactName': 'Ann Devon', 'ContactTitle': 'Sales Agent', 'Address': '35 King George', 'City': 'London', 'Region': null, 'PostalCode': 'WX3 6FW', 'Country': 'UK', 'Phone': '(171) 555-0297', 'Fax': '(171) 555-3373' }, + { 'ID': 'ERNSH', 'CompanyName': 'Ernst Handel', 'ContactName': 'Roland Mendel', 'ContactTitle': 'Sales Manager', 'Address': 'Kirchgasse 6', 'City': 'Graz', 'Region': null, 'PostalCode': '8010', 'Country': 'Austria', 'Phone': '7675-3425', 'Fax': '7675-3426' }, + { 'ID': 'FAMIA', 'CompanyName': 'Familia Arquibaldo', 'ContactName': 'Aria Cruz', 'ContactTitle': 'Marketing Assistant', 'Address': 'Rua Orós, 92', 'City': 'Sao Paulo', 'Region': 'SP', 'PostalCode': '05442-030', 'Country': 'Brazil', 'Phone': '(11) 555-9857', 'Fax': null }, + { 'ID': 'FISSA', 'CompanyName': 'FISSA Fabrica Inter. Salchichas S.A.', 'ContactName': 'Diego Roel', 'ContactTitle': 'Accounting Manager', 'Address': 'C/ Moralzarzal, 86', 'City': 'Madrid', 'Region': null, 'PostalCode': '28034', 'Country': 'Spain', 'Phone': '(91) 555 94 44', 'Fax': '(91) 555 55 93' }, + { 'ID': 'FOLIG', 'CompanyName': 'Folies gourmandes', 'ContactName': 'Martine Rancé', 'ContactTitle': 'Assistant Sales Agent', 'Address': '184, chaussée de Tournai', 'City': 'Lille', 'Region': null, 'PostalCode': '59000', 'Country': 'France', 'Phone': '20.16.10.16', 'Fax': '20.16.10.17' }, + { 'ID': 'FOLKO', 'CompanyName': 'Folk och fä HB', 'ContactName': 'Maria Larsson', 'ContactTitle': 'Owner', 'Address': 'Åkergatan 24', 'City': 'Bräcke', 'Region': null, 'PostalCode': 'S-844 67', 'Country': 'Sweden', 'Phone': '0695-34 67 21', 'Fax': null }, + { 'ID': 'FRANK', 'CompanyName': 'Frankenversand', 'ContactName': 'Peter Franken', 'ContactTitle': 'Marketing Manager', 'Address': 'Berliner Platz 43', 'City': 'München', 'Region': null, 'PostalCode': '80805', 'Country': 'Germany', 'Phone': '089-0877310', 'Fax': '089-0877451' }, + { 'ID': 'FRANR', 'CompanyName': 'France restauration', 'ContactName': 'Carine Schmitt', 'ContactTitle': 'Marketing Manager', 'Address': '54, rue Royale', 'City': 'Nantes', 'Region': null, 'PostalCode': '44000', 'Country': 'France', 'Phone': '40.32.21.21', 'Fax': '40.32.21.20' }, + { 'ID': 'FRANS', 'CompanyName': 'Franchi S.p.A.', 'ContactName': 'Paolo Accorti', 'ContactTitle': 'Sales Representative', 'Address': 'Via Monte Bianco 34', 'City': 'Torino', 'Region': null, 'PostalCode': '10100', 'Country': 'Italy', 'Phone': '011-4988260', 'Fax': '011-4988261' } + ]; + // tslint:enable:max-line-length + } +} diff --git a/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.ts b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.ts new file mode 100644 index 00000000000..fd439018c52 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-pinning-actions.component.ts @@ -0,0 +1,88 @@ +import { Component, HostBinding } from '@angular/core'; +import { IgxGridActionsBaseDirective } from './grid-actions-base.directive'; + +@Component({ + selector: 'igx-grid-pinning-actions', + templateUrl: 'grid-pinning-actions.component.html', + providers: [{ provide: IgxGridActionsBaseDirective, useExisting: IgxGridPinningActionsComponent }] +}) + +export class IgxGridPinningActionsComponent extends IgxGridActionsBaseDirective { + /** + * Host `class.igx-action-strip` binding. + * @hidden + * @internal + */ + @HostBinding('class.igx-action-strip__pining-actions') + public cssClass = 'igx-action-strip__pining-actions'; + + private iconsRendered = false; + + /** + * Getter to know if the row is pinned + * @hidden + * @internal + */ + get pinned(): boolean { + if (!this.isRow(this.strip.context)) { + return; + } + const context = this.strip.context; + if (context && !this.iconsRendered) { + this.renderIcons(); + this.iconsRendered = true; + } + return context && context.pinned; + } + + /** + * Pin the row according to the context. + * @example + * ```typescript + * this.gridPinningActions.pin(); + * ``` + */ + public pin(event?): void { + if (event) { + event.stopPropagation(); + } + if (!this.isRow(this.strip.context)) { + return; + } + const row = this.strip.context; + const grid = row.grid; + grid.pinRow(row.rowID); + this.strip.hide(); + } + + /** + * Unpin the row according to the context. + * @example + * ```typescript + * this.gridPinningActions.unpin(); + * ``` + */ + public unpin(event?): void { + if (event) { + event.stopPropagation(); + } + if (!this.isRow(this.strip.context)) { + return; + } + const row = this.strip.context; + const grid = row.grid; + grid.unpinRow(row.rowID); + this.strip.hide(); + } + + private renderIcons(): void { + if (!this.isRow(this.strip.context)) { + return; + } + const context = this.strip.context; + const grid = context.grid; + if (grid) { + grid.filteringService.registerSVGIcons(); + } + } +} diff --git a/projects/igniteui-angular/src/lib/action-strip/index.ts b/projects/igniteui-angular/src/lib/action-strip/index.ts new file mode 100644 index 00000000000..34ddbdee7b1 --- /dev/null +++ b/projects/igniteui-angular/src/lib/action-strip/index.ts @@ -0,0 +1,5 @@ +export { IgxGridActionsBaseDirective } from './grid-actions/grid-actions-base.directive'; +export { IgxGridEditingActionsComponent } from './grid-actions/grid-editing-actions.component'; +export { IgxGridPinningActionsComponent } from './grid-actions/grid-pinning-actions.component'; +export { IgxActionStripComponent } from './action-strip.component'; +export * from './action-strip.module'; diff --git a/projects/igniteui-angular/src/lib/core/styles/components/action-strip/_action-strip-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/action-strip/_action-strip-component.scss new file mode 100644 index 00000000000..cfb6f14bb5c --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/components/action-strip/_action-strip-component.scss @@ -0,0 +1,38 @@ +//// +/// @group components +/// @author Simeon Simeonoff +/// @requires {mixin} bem-block +/// @requires {mixin} bem-elem +/// @requires {mixin} bem-mod +//// +@include b(igx-action-strip) { + // Register the component in the component registry + $this: bem--selector-to-string(&); + @include register-component(str-slice($this, 2, -1)); + + @extend %igx-action-strip-display !optional; + + @include m(cosy) { + @extend %igx-action-strip--cosy !optional; + } + + @include m(compact) { + @extend %igx-action-strip--compact !optional; + } + + @include e(actions) { + @extend %igx-action-strip__actions !optional; + } + + @include e(delete) { + @extend %igx-action-strip__delete !optional; + } + + @include e(editing-actions) { + @extend %igx-action-strip__editing-actions !optional; + } + + @include e(pining-actions) { + @extend %igx-action-strip__pining-actions !optional; + } +} diff --git a/projects/igniteui-angular/src/lib/core/styles/components/action-strip/_action-strip-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/action-strip/_action-strip-theme.scss new file mode 100644 index 00000000000..d3e1e030196 --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/components/action-strip/_action-strip-theme.scss @@ -0,0 +1,172 @@ +//// +/// @group themes +/// @access public +/// @author Simeon Simeonoff +/// @author Marin Popov +//// +/// +/// If only background color is specified, text/icon color will be assigned automatically to a contrasting color. +/// @param {Map} $palette [$default-palette] - The palette used as basis for styling the component. +/// @param {Map} $schema [$light-schema] - The schema used as basis for styling the component. +/// +/// @param {Color} $color [null] - The color used for the actions icons. +/// @param {Color} $background [null] - The color used for the action strip component content background. +/// @param {Color} $actions-background [null] - The color used for the actions background. +/// @param {Color} $delete-action [null] - The color used for the delete icon in action strip component. +/// @param {actions-border-radius} $actions-border-radius [null] - The border radius used for actions container inside action strip component. +/// +/// @requires extend +/// @requires round-borders +/// @requires apply-palette +/// @requires text-contrast +/// +/// @example scss Change the background and icon colors in action strip +/// $my-action-strip-theme: igx-action-strip-theme($background: black); +/// // Pass the theme to the igx-action-strip component mixin +/// @include igx-action-strip($my-action-strip-theme); +@function igx-action-strip-theme( + $palette: $default-palette, + $schema: $light-schema, + + $background: null, + $actions-background: null, + $color: null, + $delete-action: null, + $actions-border-radius: null, +) { + $name: 'igx-action-strip'; + $action-strip-schema: (); + + @if map-has-key($schema, $name) { + $action-strip-schema: map-get($schema, $name); + } @else { + $action-strip-schema: $schema; + } + + $theme: apply-palette($action-strip-schema, $palette); + + $actions-border-radius: round-borders( + if($actions-border-radius, $actions-border-radius, map-get($action-strip-schema, 'actions-border-radius')), 0, 24px + ); + + @if not($color) and $actions-background { + $color: text-contrast($actions-background); + } + + @return extend($theme, ( + name: $name, + palette: $palette, + background: $background, + actions-background: $actions-background, + color: $color, + delete-action: $delete-action, + actions-border-radius: $actions-border-radius, + )); +} + +/// @param {Map} $theme - The theme used to style the component. +/// @requires {mixin} igx-root-css-vars +/// @requires rem +/// @requires --var +@mixin igx-action-strip($theme) { + @include igx-root-css-vars($theme); + + $padding: ( + comfortable: 0 rem(24px), + cosy: 0 rem(16px), + compact: 0 rem(12px) + ); + + $left: if-ltr(left, right); + $right: if-ltr(right, left); + + %igx-action-strip-display { + display: flex; + align-items: center; + justify-content: flex-end; + position: absolute; + width: 100%; + height: 100%; + pointer-events: none; + top: 0; + #{$left}: 0; + background: --var($theme, 'background'); + color: inherit; + padding: map-get($padding, 'comfortable'); + z-index: 9999; + } + + %action-icons-density { + [igxButton='icon'] { + width: rem(28px); + height: rem(28px); + + igx-icon { + width: rem(14px); + height: rem(14px); + font-size: rem(14px); + } + } + } + + %igx-action-strip--cosy{ + padding: map-get($padding, 'cosy'); + @extend %action-icons-density; + } + + %igx-action-strip--compact{ + padding: map-get($padding, 'compact'); + @extend %action-icons-density; + } + + %igx-action-strip__editing-actions, + %igx-action-strip__pining-actions { + display: flex; + align-items: center; + justify-content: center; + } + + %igx-action-strip__actions { + display: inline-flex; + align-items: center; + justify-content: center; + pointer-events: all; + position: relative; + color: --var($theme, 'color'); + border-radius: --var($theme, 'actions-border-radius'); + background: --var($theme, 'actions-background'); + max-height: 36px; + + &:last-child { + margin-#{$right}: 0; + } + + igx-icon { + color: --var($theme, 'color'); + } + } + + %igx-action-strip__pining-actions { + margin-#{$right}: rem(4px); + + &:last-child { + margin-#{$right}: 0; + } + } + + %igx-action-strip__editing-actions { + > [igxButton] { + margin-#{$left}: rem(4px); + + &:first-of-type { + margin-#{$left}: 0; + } + } + } + + %igx-action-strip__delete { + igx-icon { + color: --var($theme, 'delete-action'); + } + } +} diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/_core.scss b/projects/igniteui-angular/src/lib/core/styles/themes/_core.scss index 8e335642c48..08dbbda9a39 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/_core.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/_core.scss @@ -20,6 +20,7 @@ @import '../components/ripple/ripple-component'; // Component composition styles +@import '../components/action-strip/action-strip-component'; @import '../components/avatar/avatar-component'; @import '../components/badge/badge-component'; @import '../components/bottom-nav/bottom-nav-component'; diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss index bdacac534b4..c241bf2a1ea 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/_index.scss @@ -7,6 +7,7 @@ @import 'core'; // Import all component mixins +@import '../components/action-strip/action-strip-theme'; @import '../components/avatar/avatar-theme'; @import '../components/badge/badge-theme'; @import '../components/bottom-nav/bottom-nav-theme'; @@ -119,6 +120,10 @@ @include igx-avatar(igx-avatar-theme($palette, $schema)); } + @if not(index($exclude, 'igx-action-strip')) { + @include igx-action-strip(igx-action-strip-theme($palette, $schema)); + } + @if not(index($exclude, 'igx-badge')) { @include igx-badge(igx-badge-theme( $palette, diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_action-strip.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_action-strip.scss new file mode 100644 index 00000000000..8e1aa29efba --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_action-strip.scss @@ -0,0 +1,46 @@ +@import '../light/action-strip'; +//// +/// @group schemas +/// @access private +/// @author Marin Popov +//// +/// +/// Generates a dark action-strip schema. +/// @type {Map} +/// @property {Map} actions-background [igx-color: ('grays', 200), hexrgba: #000, rgba: .8]- actions container background. +/// @see $default-palette +$_dark-action-strip: extend( + $_light-action-strip, + ( + variant: 'material', + + actions-background: ( + igx-color: ('grays', 200), + hexrgba: #222, + rgba: .8 + ), + ) +); + +/// Generates a dark fluent action strip schema. +/// @type {Map} +/// @requires {function} extend +/// @requires $_dark-action-strip +$_dark-fluent-action-strip: extend( + $_dark-action-strip, + ( + variant: 'fluent', + ) +); + +/// Generates a dark bootstrap action strip schema. +/// @type {Map} +/// @requires {function} extend +/// @requires $_dark-action-strip +$_dark-bootstrap-action-strip: extend( + $_dark-action-strip, + ( + variant: 'bootstrap', + ) +); + diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss index a16012b71f8..4fecf244869 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/dark/_index.scss @@ -3,6 +3,7 @@ /// @access public /// @author Simeon Simeonoff //// +@import './action-strip'; @import './avatar'; @import './badge'; @import './banner'; @@ -91,6 +92,7 @@ /// @property {Map} igx-tooltip [$_dark-tooltip] $dark-schema: ( igx-avatar: $_dark-avatar, + igx-action-strip: $_dark-action-strip, igx-badge: $_dark-badge, igx-banner: $_dark-banner, igx-bottom-nav: $_dark-bottom-nav, @@ -180,6 +182,7 @@ $dark-schema: ( /// @property {map} igx-tooltip [$_dark-fluent-tooltip] $dark-fluent-schema: ( igx-avatar: $_dark-fluent-avatar, + igx-action-strip: $_dark-fluent-action-strip, igx-badge: $_dark-fluent-badge, igx-banner: $_dark-fluent-banner, igx-bottom-nav: $_dark-fluent-bottom-nav, @@ -269,6 +272,7 @@ $dark-fluent-schema: ( /// @property {map} igx-tooltip [$_dark-bootstrap-tooltip] $dark-bootstrap-schema: ( igx-avatar: $_dark-bootstrap-avatar, + igx-action-strip: $_dark-bootstrap-action-strip, igx-badge: $_dark-bootstrap-badge, igx-banner: $_dark-bootstrap-banner, igx-bottom-nav: $_dark-bootstrap-bottom-nav, diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_action-strip.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_action-strip.scss new file mode 100644 index 00000000000..aa07a47a20f --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_action-strip.scss @@ -0,0 +1,64 @@ +@import '../shape/action-strip'; +//// +/// @group schemas +/// @access private +/// @author Marin Popov +//// +/// +/// Generates a light action-strip schema. +/// @type {Map} +/// @prop {Color} $color [currentColor] - The color used for the actions icons. +/// @prop {Map} background [igx-color: ('grays', 100] - The color used for the action strip component content background. +/// @prop {Map} actions-background [igx-color: ('grays', 200), hexrgba: #fff, rgba: .9] - The color used for actions background. +/// @prop {Map} delete-action [igx-color: ('error')] - The color used for the delete icon in action strip component. +/// @prop {Number} border-radius [1] - The border radius fraction, between 0-1 to be used for action strip actions container. +/// @see $default-palette +$_light-action-strip: extend( + $_default-shape-action-strip, + ( + variant: 'material', + + actions-background: ( + igx-color: ('grays', 200), + hexrgba: #fff, + rgba: .9 + ), + + background: ( + igx-color: ('grays', 100) + ), + + color: currentColor, + + delete-action: ( + igx-color: ('error') + ), + ) +); + +/// Generates a fluent action strip schema. +/// @type {Map} +/// @prop {Number} border-radius [0] - The border radius fraction, between 0-1 to be used for action strip actions container.. +/// @requires {function} extend +/// @requires $_light-action-strip +$_fluent-action-strip: extend( + $_light-action-strip, + $_fluent-shape-action-strip, + ( + variant: 'fluent', + ) +); + +/// Generates a bootstrap action strip schema. +/// @type {Map} +/// @prop {Number} border-radius [4px] - The border radius fraction, between 0-1 to be used for action strip actions container. +/// @requires {function} extend +/// @requires $_light-action-strip +$_bootstrap-action-strip: extend( + $_light-action-strip, + $_bootstrap-shape-action-strip, + ( + variant: 'bootstrap', + ) +); + diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss index 4f67c8e036e..f1268d367e6 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_index.scss @@ -5,6 +5,7 @@ //// @import './avatar'; +@import './action-strip'; @import './badge'; @import './banner'; @import './bottom-nav'; @@ -49,6 +50,7 @@ /// The default light schema for all components. /// @type {Map} /// @property {Map} igx-avatar [$_light-avatar] +/// @property {Map} igx-action-strip [$_light-action-strip] /// @property {Map} igx-badge [$_light-badge] /// @property {Map} igx-banner [$_light-banner] /// @property {Map} igx-bottom-nav [$_light-bottom-nav] @@ -92,6 +94,7 @@ /// @property {Map} igx-tooltip [$_light-tooltip] $light-schema: ( igx-avatar: $_light-avatar, + igx-action-strip: $_light-action-strip, igx-badge: $_light-badge, igx-banner: $_light-banner, igx-bottom-nav: $_light-bottom-nav, @@ -137,6 +140,7 @@ $light-schema: ( $light-fluent-schema: ( igx-avatar: $_fluent-avatar, + igx-action-strip: $_fluent-action-strip, igx-badge: $_fluent-badge, igx-banner: $_fluent-banner, igx-bottom-nav: $_fluent-bottom-nav, @@ -182,6 +186,7 @@ $light-fluent-schema: ( $light-bootstrap-schema: ( igx-avatar: $_bootstrap-avatar, + igx-action-strip: $_bootstrap-action-strip, igx-badge: $_bootstrap-badge, igx-banner: $_bootstrap-banner, igx-bottom-nav: $_bootstrap-bottom-nav, diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/round-light/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/round-light/_index.scss index 216666d5f84..86aed45de2e 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/round-light/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/round-light/_index.scss @@ -4,6 +4,7 @@ /// @author Simeon Simeonoff //// +@import '../light/action-strip'; @import '../light/avatar'; @import '../light/badge'; @import '../light/banner'; @@ -46,6 +47,7 @@ /// The default light schema for all components. /// @type {Map} +/// @property {Map} igx-action-strip [$_light-action-strip] /// @property {Map} igx-avatar [$_light-avatar] /// @property {Map} igx-badge [$_light-badge] /// @property {Map} igx-banner [$_light-banner] @@ -88,6 +90,7 @@ /// @property {Map} igx-tooltip [$_light-tooltip] $light-round-schema: ( + igx-action-strip: extend($_light-action-strip, $_round-shape-avatar), igx-avatar: extend($_light-avatar, $_round-shape-avatar), igx-badge: extend($_light-badge, $_round-shape-badge), igx-banner: extend($_light-banner, $_round-shape-banner), diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/shape/_action-strip.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/shape/_action-strip.scss new file mode 100644 index 00000000000..82ffddf2187 --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/shape/_action-strip.scss @@ -0,0 +1,28 @@ +//// +/// @group schemas +/// @access private +/// @author Marin Popov +//// +$_default-shape-action-strip: ( + actions-border-radius: 1 +); + +/// @type Map +$_round-shape-action-strip: ( + actions-border-radius: 1 +); + +/// @type Map +$_square-shape-action-strip: ( + actions-border-radius: 0 +); + +/// @type Map +$_fluent-shape-action-strip: ( + actions-border-radius: 1 +); + +/// @type Map +$_bootstrap-shape-action-strip: ( + actions-border-radius: 4px +); diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/square-light/_index.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/square-light/_index.scss index 52bfc490d58..6d092fed57d 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/square-light/_index.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/square-light/_index.scss @@ -4,6 +4,7 @@ /// @author Simeon Simeonoff //// +@import '../light/action-strip'; @import '../light/avatar'; @import '../light/badge'; @import '../light/banner'; @@ -46,6 +47,7 @@ /// The default light schema for all components. /// @type {Map} +/// @property {Map} igx-action-strip [$_light-action-strip] /// @property {Map} igx-avatar [$_light-avatar] /// @property {Map} igx-badge [$_light-badge] /// @property {Map} igx-banner [$_light-banner] @@ -87,6 +89,7 @@ /// @property {Map} igx-toast [$_light-toast] /// @property {Map} igx-tooltip [$_light-tooltip] $light-square-schema: ( + igx-action-strip: extend($_light-action-strip, $_square-shape-action-strip), igx-avatar: extend($_light-avatar, $_square-shape-avatar), igx-badge: extend($_light-badge, $_square-shape-badge), igx-banner: extend($_light-banner, $_square-shape-banner), diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 3b8919b4596..7adf85086ba 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -52,6 +52,7 @@ export * from './lib/data-operations/data-util'; /** * Components */ +export * from './lib/action-strip/index'; export * from './lib/avatar/avatar.component'; export * from './lib/badge/badge.component'; export * from './lib/banner/banner.component'; diff --git a/src/app/action-strip/action-strip.sample.html b/src/app/action-strip/action-strip.sample.html new file mode 100644 index 00000000000..d6ae725ffea --- /dev/null +++ b/src/app/action-strip/action-strip.sample.html @@ -0,0 +1,93 @@ +
+ + The Action Strip provide templatable area for one or more actions. + +
+
+
+

Display Density

+ + + +
+
+
+
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +

+ + alarm + +
+
+
+

{{result}}

+
+
+ +
+
+
Grid Pinning Action
+ + + + + + + + +
+
+ +
+
+
Grid Pinning and Editing Actions
+ + + +
+ {{val}} +
+
+
+ + + + + +
+
+
+ +
+
+
Actions in menu
+ + + + + + + + + +
+
+
+
\ No newline at end of file diff --git a/src/app/action-strip/action-strip.sample.scss b/src/app/action-strip/action-strip.sample.scss new file mode 100644 index 00000000000..8c29db4e7fd --- /dev/null +++ b/src/app/action-strip/action-strip.sample.scss @@ -0,0 +1,31 @@ +.parent { + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + width: 400px; + height: 200px; + background-color: #f9f9f9; + position: relative; +} + +.my-action-strip { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + backdrop-filter: blur(2px); +} + +.cell-template { + display: flex; + align-items: center; + justify-content: flex-start; + width:100%; + height: 100%; +} + +h5 { + margin-bottom: 10px; +} diff --git a/src/app/action-strip/action-strip.sample.ts b/src/app/action-strip/action-strip.sample.ts new file mode 100644 index 00000000000..c081f4e482b --- /dev/null +++ b/src/app/action-strip/action-strip.sample.ts @@ -0,0 +1,100 @@ +import {Component, ViewChild} from '@angular/core'; +import {IgxActionStripComponent, IgxGridComponent, DisplayDensity} from 'igniteui-angular'; + +@Component({ + selector: 'app-action-strip-sample', + styleUrls: ['action-strip.sample.scss'], + templateUrl: `action-strip.sample.html` +}) +export class ActionStripSampleComponent { + @ViewChild('actionstrip') actionStrip: IgxActionStripComponent; + @ViewChild('actionstrip1') actionStrip1: IgxActionStripComponent; + @ViewChild('grid1', { static: true }) grid1: IgxGridComponent; + public result: string; + public isVisible = false; + private counter = 0; + public comfortable = DisplayDensity.comfortable; + public cosy = DisplayDensity.cosy; + public compact = DisplayDensity.compact; + public displayDensity = this.comfortable; + + doSomeAction() { + this.result = `Clicked ${this.counter++} times`; + } + + showActions() { + this.isVisible = true; + } + + hideActions() { + this.isVisible = false; + } + + onMouseOver(event, grid, actionStrip) { + if (event.target.nodeName.toLowerCase() === "igx-grid-cell") { + const rowIndex = parseInt(event.target.attributes["data-rowindex"].value, 10); + const row = grid.getRowByIndex(rowIndex); + actionStrip.show(row); + } + } + + onMouseLeave(actionstrip, event?) { + if (!event || event.relatedTarget.nodeName.toLowerCase() !== "igx-drop-down-item") { + actionstrip.hide(); + } + } + + setDensity(density: DisplayDensity) { + this.displayDensity = density; + } + + data: any[]; + columns: any[]; + + ngOnInit(): void { + this.columns = [ + { field: 'ID', width: '200px', hidden: false }, + { field: 'CompanyName', width: '200px' }, + { field: 'ContactName', width: '200px', pinned: false }, + { field: 'ContactTitle', width: '300px', pinned: false }, + { field: 'Address', width: '250px' }, + { field: 'City', width: '200px' }, + { field: 'Region', width: '300px' }, + { field: 'PostalCode', width: '150px' }, + { field: 'Phone', width: '200px' }, + { field: 'Fax', width: '200px' } + ]; + + this.data = [ + // tslint:disable:max-line-length + { 'ID': 'ALFKI', 'CompanyName': 'Alfreds Futterkiste', 'ContactName': 'Maria Anders', 'ContactTitle': 'Sales Representative', 'Address': 'Obere Str. 57', 'City': 'Berlin', 'Region': null, 'PostalCode': '12209', 'Country': 'Germany', 'Phone': '030-0074321', 'Fax': '030-0076545' }, + { 'ID': 'ANATR', 'CompanyName': 'Ana Trujillo Emparedados y helados', 'ContactName': 'Ana Trujillo', 'ContactTitle': 'Owner', 'Address': 'Avda. de la Constitución 2222', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05021', 'Country': 'Mexico', 'Phone': '(5) 555-4729', 'Fax': '(5) 555-3745' }, + { 'ID': 'ANTON', 'CompanyName': 'Antonio Moreno Taquería', 'ContactName': 'Antonio Moreno', 'ContactTitle': 'Owner', 'Address': 'Mataderos 2312', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05023', 'Country': 'Mexico', 'Phone': '(5) 555-3932', 'Fax': null }, + { 'ID': 'AROUT', 'CompanyName': 'Around the Horn', 'ContactName': 'Thomas Hardy', 'ContactTitle': 'Sales Representative', 'Address': '120 Hanover Sq.', 'City': 'London', 'Region': null, 'PostalCode': 'WA1 1DP', 'Country': 'UK', 'Phone': '(171) 555-7788', 'Fax': '(171) 555-6750' }, + { 'ID': 'BERGS', 'CompanyName': 'Berglunds snabbköp', 'ContactName': 'Christina Berglund', 'ContactTitle': 'Order Administrator', 'Address': 'Berguvsvägen 8', 'City': 'Luleå', 'Region': null, 'PostalCode': 'S-958 22', 'Country': 'Sweden', 'Phone': '0921-12 34 65', 'Fax': '0921-12 34 67' }, + { 'ID': 'BLAUS', 'CompanyName': 'Blauer See Delikatessen', 'ContactName': 'Hanna Moos', 'ContactTitle': 'Sales Representative', 'Address': 'Forsterstr. 57', 'City': 'Mannheim', 'Region': null, 'PostalCode': '68306', 'Country': 'Germany', 'Phone': '0621-08460', 'Fax': '0621-08924' }, + { 'ID': 'BLONP', 'CompanyName': 'Blondesddsl père et fils', 'ContactName': 'Frédérique Citeaux', 'ContactTitle': 'Marketing Manager', 'Address': '24, place Kléber', 'City': 'Strasbourg', 'Region': null, 'PostalCode': '67000', 'Country': 'France', 'Phone': '88.60.15.31', 'Fax': '88.60.15.32' }, + { 'ID': 'BOLID', 'CompanyName': 'Bólido Comidas preparadas', 'ContactName': 'Martín Sommer', 'ContactTitle': 'Owner', 'Address': 'C/ Araquil, 67', 'City': 'Madrid', 'Region': null, 'PostalCode': '28023', 'Country': 'Spain', 'Phone': '(91) 555 22 82', 'Fax': '(91) 555 91 99' }, + { 'ID': 'BONAP', 'CompanyName': 'Bon app\'', 'ContactName': 'Laurence Lebihan', 'ContactTitle': 'Owner', 'Address': '12, rue des Bouchers', 'City': 'Marseille', 'Region': null, 'PostalCode': '13008', 'Country': 'France', 'Phone': '91.24.45.40', 'Fax': '91.24.45.41' }, + { 'ID': 'BOTTM', 'CompanyName': 'Bottom-Dollar Markets', 'ContactName': 'Elizabeth Lincoln', 'ContactTitle': 'Accounting Manager', 'Address': '23 Tsawassen Blvd.', 'City': 'Tsawassen', 'Region': 'BC', 'PostalCode': 'T2F 8M4', 'Country': 'Canada', 'Phone': '(604) 555-4729', 'Fax': '(604) 555-3745' }, + { 'ID': 'BSBEV', 'CompanyName': 'B\'s Beverages', 'ContactName': 'Victoria Ashworth', 'ContactTitle': 'Sales Representative', 'Address': 'Fauntleroy Circus', 'City': 'London', 'Region': null, 'PostalCode': 'EC2 5NT', 'Country': 'UK', 'Phone': '(171) 555-1212', 'Fax': null }, + { 'ID': 'CACTU', 'CompanyName': 'Cactus Comidas para llevar', 'ContactName': 'Patricio Simpson', 'ContactTitle': 'Sales Agent', 'Address': 'Cerrito 333', 'City': 'Buenos Aires', 'Region': null, 'PostalCode': '1010', 'Country': 'Argentina', 'Phone': '(1) 135-5555', 'Fax': '(1) 135-4892' }, + { 'ID': 'CENTC', 'CompanyName': 'Centro comercial Moctezuma', 'ContactName': 'Francisco Chang', 'ContactTitle': 'Marketing Manager', 'Address': 'Sierras de Granada 9993', 'City': 'México D.F.', 'Region': null, 'PostalCode': '05022', 'Country': 'Mexico', 'Phone': '(5) 555-3392', 'Fax': '(5) 555-7293' }, + { 'ID': 'CHOPS', 'CompanyName': 'Chop-suey Chinese', 'ContactName': 'Yang Wang', 'ContactTitle': 'Owner', 'Address': 'Hauptstr. 29', 'City': 'Bern', 'Region': null, 'PostalCode': '3012', 'Country': 'Switzerland', 'Phone': '0452-076545', 'Fax': null }, + { 'ID': 'COMMI', 'CompanyName': 'Comércio Mineiro', 'ContactName': 'Pedro Afonso', 'ContactTitle': 'Sales Associate', 'Address': 'Av. dos Lusíadas, 23', 'City': 'Sao Paulo', 'Region': 'SP', 'PostalCode': '05432-043', 'Country': 'Brazil', 'Phone': '(11) 555-7647', 'Fax': null }, + { 'ID': 'CONSH', 'CompanyName': 'Consolidated Holdings', 'ContactName': 'Elizabeth Brown', 'ContactTitle': 'Sales Representative', 'Address': 'Berkeley Gardens 12 Brewery', 'City': 'London', 'Region': null, 'PostalCode': 'WX1 6LT', 'Country': 'UK', 'Phone': '(171) 555-2282', 'Fax': '(171) 555-9199' }, + { 'ID': 'DRACD', 'CompanyName': 'Drachenblut Delikatessen', 'ContactName': 'Sven Ottlieb', 'ContactTitle': 'Order Administrator', 'Address': 'Walserweg 21', 'City': 'Aachen', 'Region': null, 'PostalCode': '52066', 'Country': 'Germany', 'Phone': '0241-039123', 'Fax': '0241-059428' }, + { 'ID': 'DUMON', 'CompanyName': 'Du monde entier', 'ContactName': 'Janine Labrune', 'ContactTitle': 'Owner', 'Address': '67, rue des Cinquante Otages', 'City': 'Nantes', 'Region': null, 'PostalCode': '44000', 'Country': 'France', 'Phone': '40.67.88.88', 'Fax': '40.67.89.89' }, + { 'ID': 'EASTC', 'CompanyName': 'Eastern Connection', 'ContactName': 'Ann Devon', 'ContactTitle': 'Sales Agent', 'Address': '35 King George', 'City': 'London', 'Region': null, 'PostalCode': 'WX3 6FW', 'Country': 'UK', 'Phone': '(171) 555-0297', 'Fax': '(171) 555-3373' }, + { 'ID': 'ERNSH', 'CompanyName': 'Ernst Handel', 'ContactName': 'Roland Mendel', 'ContactTitle': 'Sales Manager', 'Address': 'Kirchgasse 6', 'City': 'Graz', 'Region': null, 'PostalCode': '8010', 'Country': 'Austria', 'Phone': '7675-3425', 'Fax': '7675-3426' }, + { 'ID': 'FAMIA', 'CompanyName': 'Familia Arquibaldo', 'ContactName': 'Aria Cruz', 'ContactTitle': 'Marketing Assistant', 'Address': 'Rua Orós, 92', 'City': 'Sao Paulo', 'Region': 'SP', 'PostalCode': '05442-030', 'Country': 'Brazil', 'Phone': '(11) 555-9857', 'Fax': null }, + { 'ID': 'FISSA', 'CompanyName': 'FISSA Fabrica Inter. Salchichas S.A.', 'ContactName': 'Diego Roel', 'ContactTitle': 'Accounting Manager', 'Address': 'C/ Moralzarzal, 86', 'City': 'Madrid', 'Region': null, 'PostalCode': '28034', 'Country': 'Spain', 'Phone': '(91) 555 94 44', 'Fax': '(91) 555 55 93' }, + { 'ID': 'FOLIG', 'CompanyName': 'Folies gourmandes', 'ContactName': 'Martine Rancé', 'ContactTitle': 'Assistant Sales Agent', 'Address': '184, chaussée de Tournai', 'City': 'Lille', 'Region': null, 'PostalCode': '59000', 'Country': 'France', 'Phone': '20.16.10.16', 'Fax': '20.16.10.17' }, + { 'ID': 'FOLKO', 'CompanyName': 'Folk och fä HB', 'ContactName': 'Maria Larsson', 'ContactTitle': 'Owner', 'Address': 'Åkergatan 24', 'City': 'Bräcke', 'Region': null, 'PostalCode': 'S-844 67', 'Country': 'Sweden', 'Phone': '0695-34 67 21', 'Fax': null }, + { 'ID': 'FRANK', 'CompanyName': 'Frankenversand', 'ContactName': 'Peter Franken', 'ContactTitle': 'Marketing Manager', 'Address': 'Berliner Platz 43', 'City': 'München', 'Region': null, 'PostalCode': '80805', 'Country': 'Germany', 'Phone': '089-0877310', 'Fax': '089-0877451' }, + { 'ID': 'FRANR', 'CompanyName': 'France restauration', 'ContactName': 'Carine Schmitt', 'ContactTitle': 'Marketing Manager', 'Address': '54, rue Royale', 'City': 'Nantes', 'Region': null, 'PostalCode': '44000', 'Country': 'France', 'Phone': '40.32.21.21', 'Fax': '40.32.21.20' }, + { 'ID': 'FRANS', 'CompanyName': 'Franchi S.p.A.', 'ContactName': 'Paolo Accorti', 'ContactTitle': 'Sales Representative', 'Address': 'Via Monte Bianco 34', 'City': 'Torino', 'Region': null, 'PostalCode': '10100', 'Country': 'Italy', 'Phone': '011-4988260', 'Fax': '011-4988261' } + ]; + // tslint:enable:max-line-length + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1f00f07d7c6..9df69e49855 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -28,6 +28,11 @@ export class AppComponent implements OnInit { }; componentLinks = [ + { + link: '/action-strip', + icon: 'view_list', + name: 'Action Strip' + }, { link: '/autocomplete', icon: 'view_list', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2b06d172844..be626bb9a99 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,12 +6,13 @@ import { NgModule } from '@angular/core'; import { IgxIconModule, IgxGridModule, IgxExcelExporterService, IgxCsvExporterService, IgxOverlayService, IgxDragDropModule, IgxDividerModule, IgxTreeGridModule, IgxHierarchicalGridModule, IgxInputGroupModule, - IgxIconService, DisplayDensityToken, DisplayDensity, IgxDateTimeEditorModule, IgxButtonModule + IgxIconService, DisplayDensityToken, DisplayDensity, IgxDateTimeEditorModule, IgxButtonModule, IgxActionStripModule } from 'igniteui-angular'; import { IgxColumnHidingModule } from 'igniteui-angular'; import { SharedModule } from './shared/shared.module'; import { routing } from './routing'; +import { ActionStripSampleComponent } from './action-strip/action-strip.sample'; import { AppComponent } from './app.component'; import { AvatartSampleComponent } from './avatar/avatar.sample'; import { PageHeaderComponent } from './pageHeading/pageHeading.component'; @@ -122,6 +123,7 @@ import { ReactiveFormSampleComponent } from './reactive-from/reactive-form-sampl import { GridRowPinningSampleComponent } from './grid-row-pinning/grid-row-pinning.sample'; const components = [ + ActionStripSampleComponent, AppComponent, AutocompletePipeContains, AutocompleteGroupPipeContains, @@ -245,6 +247,7 @@ const components = [ HttpClientModule, IgxIconModule, IgxInputGroupModule, + IgxActionStripModule, IgxGridModule, IgxTreeGridModule, IgxHierarchicalGridModule, diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 0f13bb8b87a..5bd1b38595d 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -70,6 +70,7 @@ import { AboutComponent } from './grid-state/about.component'; import { GridMasterDetailSampleComponent } from './grid-master-detail/grid-master-detail.sample'; import { DateTimeEditorSampleComponent } from './date-time-editor/date-time-editor.sample'; import { GridRowPinningSampleComponent } from './grid-row-pinning/grid-row-pinning.sample'; +import { ActionStripSampleComponent } from './action-strip/action-strip.sample'; const appRoutes = [ { @@ -77,6 +78,10 @@ const appRoutes = [ pathMatch: 'full', redirectTo: '/avatar' }, + { + path: 'action-strip', + component: ActionStripSampleComponent + }, { path: 'autocomplete', component: AutocompleteSampleComponent diff --git a/src/app/grid-column-pinning/grid-column-pinning.sample.css b/src/app/grid-column-pinning/grid-column-pinning.sample.css index 10e8c0afa67..20cb9c4232a 100644 --- a/src/app/grid-column-pinning/grid-column-pinning.sample.css +++ b/src/app/grid-column-pinning/grid-column-pinning.sample.css @@ -6,3 +6,17 @@ [igxButton]+[igxButton] { margin-left: 8px; } + +.pin-icon { + text-align: center; +} + +.igx-action-strip { + background-color: #00000080; +} + +.actions { + margin: auto; + width: fit-content; + padding: 8px; +} \ No newline at end of file diff --git a/src/app/grid-column-pinning/grid-column-pinning.sample.html b/src/app/grid-column-pinning/grid-column-pinning.sample.html index 846dbba84f8..44a5f432ced 100644 --- a/src/app/grid-column-pinning/grid-column-pinning.sample.html +++ b/src/app/grid-column-pinning/grid-column-pinning.sample.html @@ -36,4 +36,4 @@ - + \ No newline at end of file diff --git a/src/app/grid-column-pinning/grid-column-pinning.sample.ts b/src/app/grid-column-pinning/grid-column-pinning.sample.ts index b722af11047..e22ff32cdb7 100644 --- a/src/app/grid-column-pinning/grid-column-pinning.sample.ts +++ b/src/app/grid-column-pinning/grid-column-pinning.sample.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { IgxGridComponent, ColumnPinningPosition, RowPinningPosition, GridSelectionMode } from 'igniteui-angular'; +import { IgxGridComponent, ColumnPinningPosition, RowPinningPosition, GridSelectionMode, IgxGridRowComponent } from 'igniteui-angular'; import { IPinningConfig } from 'projects/igniteui-angular/src/lib/grids/common/grid.interface'; @Component({ @@ -115,4 +115,10 @@ export class GridColumnPinningSampleComponent implements OnInit { this.selectionMode = this.selectionMode === GridSelectionMode.none ? GridSelectionMode.multiple : GridSelectionMode.none; } + doSomeAction(row?: IgxGridRowComponent) { + !this.grid1.isRecordPinned(row.rowData) ? + this.grid1.pinRow(row.rowData) : + this.grid1.unpinRow(row.rowData) + } + } diff --git a/src/app/routing.ts b/src/app/routing.ts index 413ce8b5ef8..bf6d9cd0b8f 100644 --- a/src/app/routing.ts +++ b/src/app/routing.ts @@ -97,6 +97,7 @@ import { GridMasterDetailSampleComponent } from './grid-master-detail/grid-maste import { DateTimeEditorSampleComponent } from './date-time-editor/date-time-editor.sample'; import { GridRowPinningSampleComponent } from './grid-row-pinning/grid-row-pinning.sample'; import { ReactiveFormSampleComponent } from './reactive-from/reactive-form-sample.component'; +import { ActionStripSampleComponent } from './action-strip/action-strip.sample'; const appRoutes = [ { @@ -104,6 +105,10 @@ const appRoutes = [ pathMatch: 'full', redirectTo: '/avatar' }, + { + path: 'action-strip', + component: ActionStripSampleComponent + }, { path: 'autocomplete', component: AutocompleteSampleComponent diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 990f2026a79..2f5e27275ff 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { + IgxActionStripModule, IgxAutocompleteModule, IgxAvatarModule, IgxBadgeModule, @@ -43,6 +44,7 @@ import { const igniteModules = [ + IgxActionStripModule, IgxAutocompleteModule, IgxAvatarModule, IgxBadgeModule,