Skip to content

Commit 00ea3e6

Browse files
Merge branch 'master' into ibarakov/fix-6973-9.1.x
2 parents c7aa3e2 + 079f58d commit 00ea3e6

38 files changed

+1754
-4
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ All notable changes for each version of this project will be documented in this
1919

2020
### New Features
2121

22+
- `IgxActionStrip` component added.
23+
- Provides a template area for one or more actions. In its simplest form the Action Strip
24+
is an overlay of any container and shows additional content over that container.
25+
26+
```html
27+
<igx-action-strip #actionstrip>
28+
<igx-icon (click)="doSomeAction()"></igx-icon>
29+
</igx-action-strip>
30+
```
31+
2232
- `igxSplitter` component added.
2333
- Allows rendering a vertical or horizontal splitter with multiple splitter panes with templatable content.
2434
Panes can be resized or collapsed/expanded via the UI. Splitter orientation is defined via the `type` input.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# igx-action-strip
2+
3+
The **igx-action-strip** provides a template area for one or more actions.
4+
In its simplest form the Action Strip is an overlay of any container and shows additional content over that container.
5+
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)
6+
7+
# Usage
8+
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.
9+
```html
10+
<igx-action-strip #actionstrip>
11+
<igx-icon (click)="doSomeAction()"></igx-icon>
12+
</igx-action-strip>
13+
```
14+
15+
# Grid Action Components
16+
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`.
17+
18+
```html
19+
<igx-action-strip #actionstrip>
20+
<igx-grid-editing-actions [grid]="grid1"></igx-grid-editing-actions>
21+
<igx-grid-pinning-actions [grid]="grid1"></igx-grid-pinning-actions>
22+
</igx-action-strip>
23+
```
24+
25+
# IgxActionStripMenuItem
26+
27+
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.
28+
29+
```html
30+
<igx-action-strip #actionStrip1>
31+
<span *IgxActionStripMenuItem>Copy</span>
32+
<span *IgxActionStripMenuItem>Paste</span>
33+
<span *IgxActionStripMenuItem>Edit</span>
34+
</igx-action-strip>
35+
```
36+
# API Summary
37+
38+
## Inputs
39+
`IgxActionStripComponent`
40+
41+
| Name | Description | Type | Default value |
42+
|-----------------|---------------------------------------------------|-----------------------------|---------------|
43+
| hidden | An @Input property that sets the visibility of the Action Strip. | boolean | `false` |
44+
| 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 | |
45+
46+
`IgxGridActionsBaseDirective` ( `IgxGridPinningActionsComponent`, `IgxGridEditingActionsComponent`)
47+
48+
| Name | Description | Type | Default value |
49+
|-----------------|---------------------------------------------------|-----------------------------|---------------|
50+
| grid | Set an instance of the grid for which to display the actions. | any | |
51+
| context | Sets the context of an action strip. The context is expected to be grid cell or grid row | any | |
52+
53+
## Outputs
54+
|Name|Description|Cancelable|Parameters|
55+
|--|--|--|--|
56+
| onMenuOpening | Emitted before the menu is opened | true | |
57+
| onMenuOpened | Emitted after the menu is opened | false | |
58+
59+
## Methods
60+
61+
`IgxActionStripComponent`
62+
63+
| Name | Description | Return type | Parameters |
64+
|----------|----------------------------|---------------------------------------------------|----------------------|
65+
| show | Showing the Action Strip and appending it the specified context element. | void | context |
66+
| hide | Hiding the Action Strip and removing it from its current context element. | void | |
67+
68+
`IgxGridPinningActionsComponent`
69+
| Name | Description | Return type | Parameters |
70+
|----------|----------------------------|---------------------------------------------------|----------------------|
71+
| pin | Pin the row according to the context. | void | |
72+
| unpin | Unpin the row according to the context. | void | |
73+
74+
`IgxGridPinningActionsComponent`
75+
| Name | Description | Return type | Parameters |
76+
|----------|----------------------------|---------------------------------------------------|----------------------|
77+
| startEdit | Enter row or cell edit mode depending the grid `rowEdibable` option | void | |
78+
| deleteRow | Delete a row according to the context | void | |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div class="igx-action-strip__actions">
2+
<ng-content #content></ng-content>
3+
<ng-container *ngIf="menuItems.length > 0">
4+
<button igxButton="icon" igxRipple [igxToggleAction]="dropdown"
5+
[overlaySettings]="menuOverlaySettings" (click)="$event.stopPropagation()"
6+
[igxDropDownItemNavigation]="dropdown">
7+
<igx-icon>more_vert</igx-icon>
8+
</button>
9+
<igx-drop-down #dropdown>
10+
<igx-drop-down-item *ngFor="let item of menuItems">
11+
<div class="igx-drop-down__item-template">
12+
<ng-container *ngTemplateOutlet="item.templateRef; context: {$implicit: item}"></ng-container>
13+
</div>
14+
</igx-drop-down-item>
15+
</igx-drop-down>
16+
</ng-container>
17+
</div>
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import { IgxActionStripComponent } from './action-strip.component';
2+
import { Component, ViewChild, ElementRef, ViewContainerRef } from '@angular/core';
3+
import { configureTestSuite } from '../test-utils/configure-suite';
4+
import { TestBed, async } from '@angular/core/testing';
5+
import { IgxIconModule } from '../icon';
6+
import { By } from '@angular/platform-browser';
7+
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
8+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
9+
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
10+
import { IgxActionStripModule } from './action-strip.module';
11+
12+
const ACTION_STRIP_CONTAINER_CSS = 'igx-action-strip__actions';
13+
const DROP_DOWN_LIST = 'igx-drop-down__list';
14+
15+
describe('igxActionStrip', () => {
16+
let fixture;
17+
let actionStrip: IgxActionStripComponent;
18+
let actionStripElement: ElementRef;
19+
let parentContainer: ElementRef;
20+
let innerContainer: ViewContainerRef;
21+
22+
describe('Unit tests: ', () => {
23+
const mockViewContainerRef = jasmine.createSpyObj('ViewContainerRef', ['element']);
24+
const mockRenderer2 = jasmine.createSpyObj('Renderer2', ['appendChild', 'removeChild']);
25+
const mockContext = jasmine.createSpyObj('context', ['element']);
26+
const mockDisplayDensity = jasmine.createSpyObj('IDisplayDensityOptions', ['displayDensity']);
27+
28+
it('should properly get/set hidden', () => {
29+
actionStrip = new IgxActionStripComponent(mockViewContainerRef, mockRenderer2, mockDisplayDensity);
30+
expect(actionStrip.hidden).toBeFalsy();
31+
actionStrip.hidden = true;
32+
expect(actionStrip.hidden).toBeTruthy();
33+
});
34+
35+
it('should properly show and hide using API', () => {
36+
actionStrip = new IgxActionStripComponent(mockViewContainerRef, mockRenderer2, mockDisplayDensity);
37+
actionStrip.show(mockContext);
38+
expect(actionStrip.hidden).toBeFalsy();
39+
expect(actionStrip.context).toBe(mockContext);
40+
actionStrip.hide();
41+
expect(actionStrip.hidden).toBeTruthy();
42+
});
43+
});
44+
45+
describe('Initialization and rendering tests: ', () => {
46+
configureTestSuite();
47+
beforeAll(async(() => {
48+
TestBed.configureTestingModule({
49+
declarations: [
50+
IgxActionStripTestingComponent
51+
],
52+
imports: [
53+
IgxActionStripModule,
54+
IgxIconModule
55+
]
56+
}).compileComponents();
57+
}));
58+
beforeEach(() => {
59+
fixture = TestBed.createComponent(IgxActionStripTestingComponent);
60+
fixture.detectChanges();
61+
actionStrip = fixture.componentInstance.actionStrip;
62+
actionStripElement = fixture.componentInstance.actionStripElement;
63+
parentContainer = fixture.componentInstance.parentContainer;
64+
innerContainer = fixture.componentInstance.innerContainer;
65+
});
66+
it('should be overlapping its parent container when no context is applied', () => {
67+
const parentBoundingRect = parentContainer.nativeElement.getBoundingClientRect();
68+
const actionStripBoundingRect = actionStripElement.nativeElement.getBoundingClientRect();
69+
expect(parentBoundingRect.top).toBe(actionStripBoundingRect.top);
70+
expect(parentBoundingRect.bottom).toBe(actionStripBoundingRect.bottom);
71+
expect(parentBoundingRect.left).toBe(actionStripBoundingRect.left);
72+
expect(parentBoundingRect.right).toBe(actionStripBoundingRect.right);
73+
});
74+
75+
it('should be overlapping context.element when context is applied', () => {
76+
actionStrip.show(innerContainer);
77+
fixture.detectChanges();
78+
const innerBoundingRect = innerContainer.element.nativeElement.getBoundingClientRect();
79+
const actionStripBoundingRect = actionStripElement.nativeElement.getBoundingClientRect();
80+
expect(innerBoundingRect.top).toBe(actionStripBoundingRect.top);
81+
expect(innerBoundingRect.bottom).toBe(actionStripBoundingRect.bottom);
82+
expect(innerBoundingRect.left).toBe(actionStripBoundingRect.left);
83+
expect(innerBoundingRect.right).toBe(actionStripBoundingRect.right);
84+
});
85+
86+
it('should allow interacting with the content elements', () => {
87+
const asIcon = fixture.debugElement.query(By.css('.asIcon'));
88+
asIcon.triggerEventHandler('click', new Event('click'));
89+
fixture.detectChanges();
90+
expect(fixture.componentInstance.flag).toBeTruthy();
91+
});
92+
93+
it('should not display the action strip when setting it hidden', () => {
94+
actionStrip.hidden = true;
95+
fixture.detectChanges();
96+
const asQuery = fixture.debugElement.query(By.css('igx-action-strip'));
97+
expect(asQuery.nativeElement.style.display).toBe('none');
98+
});
99+
});
100+
101+
describe('render content as menu', () => {
102+
configureTestSuite();
103+
beforeAll(async(() => {
104+
TestBed.configureTestingModule({
105+
declarations: [
106+
IgxActionStripMenuTestingComponent,
107+
IgxActionStripCombinedMenuTestingComponent
108+
],
109+
imports: [
110+
IgxActionStripModule,
111+
IgxIconModule,
112+
NoopAnimationsModule,
113+
IgxToggleModule
114+
]
115+
}).compileComponents();
116+
}));
117+
118+
it('should render tree-dot button which toggles the content as menu', () => {
119+
fixture = TestBed.createComponent(IgxActionStripMenuTestingComponent);
120+
fixture.detectChanges();
121+
actionStrip = fixture.componentInstance.actionStrip;
122+
const actionStripContainer = fixture.debugElement.query(By.css(`.${ACTION_STRIP_CONTAINER_CSS}`));
123+
// there should be one rendered child and one hidden dropdown
124+
expect(actionStripContainer.nativeElement.children.length).toBe(2);
125+
let dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`));
126+
expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true');
127+
const icon = fixture.debugElement.query(By.css(`igx-icon`));
128+
icon.parent.triggerEventHandler('click', new Event('click'));
129+
fixture.detectChanges();
130+
dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`));
131+
expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('false');
132+
const dropDownItems = dropDownList.queryAll(By.css('igx-drop-down-item'));
133+
expect(dropDownItems.length).toBe(3);
134+
});
135+
136+
it('should emit onMenuOpen/onMenuOpening when toggling the menu', () => {
137+
pending('implementation');
138+
});
139+
140+
it('should allow combining content outside and inside the menu', () => {
141+
fixture = TestBed.createComponent(IgxActionStripCombinedMenuTestingComponent);
142+
fixture.detectChanges();
143+
actionStrip = fixture.componentInstance.actionStrip;
144+
const actionStripContainer = fixture.debugElement.query(By.css(`.${ACTION_STRIP_CONTAINER_CSS}`));
145+
// there should be one rendered child and one hidden dropdown and one additional icon
146+
expect(actionStripContainer.nativeElement.children.length).toBe(3);
147+
let dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`));
148+
expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true');
149+
const icon = fixture.debugElement.query(By.css(`igx-icon`));
150+
icon.parent.triggerEventHandler('click', new Event('click'));
151+
fixture.detectChanges();
152+
dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`));
153+
expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('false');
154+
const dropDownItems = dropDownList.queryAll(By.css('igx-drop-down-item'));
155+
expect(dropDownItems.length).toBe(2);
156+
});
157+
158+
it('should close the menu when hiding action strip', async() => {
159+
fixture = TestBed.createComponent(IgxActionStripCombinedMenuTestingComponent);
160+
fixture.detectChanges();
161+
actionStrip = fixture.componentInstance.actionStrip;
162+
// there should be one rendered child and one hidden dropdown and one additional icon
163+
const icon = fixture.debugElement.query(By.css(`igx-icon`));
164+
icon.parent.triggerEventHandler('click', new Event('click'));
165+
fixture.detectChanges();
166+
let dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`));
167+
expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('false');
168+
actionStrip.hide();
169+
await wait();
170+
fixture.detectChanges();
171+
dropDownList = fixture.debugElement.query(By.css(`.${DROP_DOWN_LIST}`));
172+
expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true');
173+
});
174+
});
175+
});
176+
177+
@Component({
178+
template: `
179+
<div #parent style="position:relative; height: 200px; width: 400px;">
180+
<div #inner style="position:relative; height: 100px; width: 200px;">
181+
<p>
182+
Lorem ipsum dolor sit
183+
</p>
184+
</div>
185+
<igx-action-strip #actionStrip>
186+
<igx-icon class="asIcon" (click)="onIconClick()">alarm</igx-icon>
187+
</igx-action-strip>
188+
</div>
189+
`
190+
})
191+
class IgxActionStripTestingComponent {
192+
@ViewChild('actionStrip', { read: IgxActionStripComponent, static: true })
193+
public actionStrip: IgxActionStripComponent;
194+
195+
@ViewChild('actionStrip', { read: ElementRef, static: true })
196+
public actionStripElement: ElementRef;
197+
198+
@ViewChild('parent', { static: true })
199+
public parentContainer: ElementRef;
200+
201+
@ViewChild('inner', { read: ViewContainerRef, static: true })
202+
public innerContainer: ViewContainerRef;
203+
204+
public flag = false;
205+
206+
onIconClick() {
207+
this.flag = true;
208+
}
209+
}
210+
211+
@Component({
212+
template: `
213+
<div #parent style="position:relative; height: 200px; width: 400px;">
214+
<div>
215+
<p>
216+
Lorem ipsum dolor sit
217+
</p>
218+
</div>
219+
<igx-action-strip #actionStrip>
220+
<span *igxActionStripMenuItem>Mark</span>
221+
<span *igxActionStripMenuItem>Favorite</span>
222+
<span *igxActionStripMenuItem>Download</span>
223+
</igx-action-strip>
224+
</div>
225+
`
226+
})
227+
class IgxActionStripMenuTestingComponent {
228+
@ViewChild('actionStrip', { read: IgxActionStripComponent, static: true })
229+
public actionStrip: IgxActionStripComponent;
230+
}
231+
232+
@Component({
233+
template: `
234+
<div #parent style="position:relative; height: 200px; width: 400px;">
235+
<div>
236+
<p>
237+
Lorem ipsum dolor sit
238+
</p>
239+
</div>
240+
<igx-action-strip #actionStrip>
241+
<span>Mark</span>
242+
<span *igxActionStripMenuItem>Favorite</span>
243+
<span *igxActionStripMenuItem>Download</span>
244+
</igx-action-strip>
245+
</div>
246+
`
247+
})
248+
class IgxActionStripCombinedMenuTestingComponent {
249+
@ViewChild('actionStrip', { read: IgxActionStripComponent, static: true })
250+
public actionStrip: IgxActionStripComponent;
251+
}

0 commit comments

Comments
 (0)