Skip to content

Commit 6b449df

Browse files
authored
Merge pull request #3055 from Akshat55/tag-components
feat: introduce selectable & operational tags
2 parents a119742 + f25bfaa commit 6b449df

File tree

9 files changed

+283
-28
lines changed

9 files changed

+283
-28
lines changed

src/tag/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export * from "./tag-filter.component";
22
export * from "./tag.component";
3+
export * from "./tag-icon.directive";
4+
export * from "./tag-selectable.component";
5+
export * from "./tag-operational.component";
36
export * from "./tag.module";

src/tag/tag-filter.component.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe("TagFilter", () => {
2929

3030
it("should call onClick on label click", () => {
3131
fixture = TestBed.createComponent(TagFilterTest);
32+
fixture.detectChanges();
3233
debugElement = fixture.debugElement.query(By.css(".cds--tag__label"));
3334
fixture.detectChanges();
3435
spyOn(debugElement.componentInstance.click, "emit");
@@ -39,6 +40,7 @@ describe("TagFilter", () => {
3940

4041
it("should call both onClick and onClose on close icon click", () => {
4142
fixture = TestBed.createComponent(TagFilterTest);
43+
fixture.detectChanges();
4244
debugElement = fixture.debugElement.query(By.css(".cds--tag__close-icon"));
4345
fixture.detectChanges();
4446
spyOn(debugElement.componentInstance.close, "emit");
@@ -52,6 +54,7 @@ describe("TagFilter", () => {
5254
it("should not call onClick when disabled", () => {
5355
fixture = TestBed.createComponent(TagFilterTest);
5456
fixture.componentInstance.disabled = true;
57+
fixture.detectChanges();
5558
debugElement = fixture.debugElement.query(By.css(".cds--tag__label"));
5659
fixture.detectChanges();
5760
spyOn(debugElement.componentInstance.click, "emit");
@@ -63,6 +66,7 @@ describe("TagFilter", () => {
6366
it("should not call onClick nor onClose when disabled", () => {
6467
fixture = TestBed.createComponent(TagFilterTest);
6568
fixture.componentInstance.disabled = true;
69+
fixture.detectChanges();
6670
debugElement = fixture.debugElement.query(By.css(".cds--tag__close-icon"));
6771
fixture.detectChanges();
6872
spyOn(debugElement.componentInstance.close, "emit");

src/tag/tag-filter.component.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,36 @@ import {
33
Output,
44
EventEmitter,
55
HostBinding,
6-
Input
6+
Input,
7+
TemplateRef
78
} from "@angular/core";
89
import { Tag } from "./tag.component";
910

1011
@Component({
1112
selector: "cds-tag-filter, ibm-tag-filter",
1213
template: `
13-
<span
14-
class="cds--tag__label"
15-
[attr.title]="title ? title : null"
16-
(click)="onClick($event)">
17-
<ng-content></ng-content>
18-
</span>
19-
<button
20-
class="cds--tag__close-icon"
21-
(click)="onClose($event)"
22-
[disabled]="disabled"
23-
[title]="closeButtonLabel">
24-
<span class="cds--visually-hidden">{{closeButtonLabel}}</span>
25-
<svg cdsIcon="close" size="16"></svg>
26-
</button>
14+
<ng-container *ngIf="!skeleton">
15+
<ng-content select="[cdsTagIcon],[ibmTagIcon]"></ng-content>
16+
<span
17+
class="cds--tag__label"
18+
[attr.title]="title ? title : null"
19+
(click)="onClick($event)">
20+
<ng-content></ng-content>
21+
</span>
22+
<button
23+
class="cds--tag__close-icon"
24+
(click)="onClose($event)"
25+
[disabled]="disabled"
26+
[title]="closeButtonLabel">
27+
<span class="cds--visually-hidden">{{closeButtonLabel}}</span>
28+
<svg cdsIcon="close" size="16"></svg>
29+
</button>
30+
</ng-container>
2731
`
2832
})
2933
export class TagFilter extends Tag {
3034
@Input() closeButtonLabel = "Clear Filter";
31-
@Input() disabled: boolean;
35+
@Input() disabled = false;
3236
@Input() title: string;
3337

3438
/**
@@ -59,8 +63,16 @@ export class TagFilter extends Tag {
5963
this.close.emit();
6064
}
6165

66+
/**
67+
* @todo
68+
* Remove `cds--tag--${this.size}` in v7
69+
*/
6270
@HostBinding("attr.class") get attrClass() {
63-
return `cds--tag cds--tag--filter cds--tag--${this.type} cds--tag--${this.size} cds--layout--size-${this.size} ${this.class}${this.disabled ? " cds--tag--disabled" : ""}`;
71+
const disabledClass = this.disabled ? "cds--tag--disabled" : "";
72+
const sizeClass = `cds--tag--${this.size} cds--layout--size-${this.size}`;
73+
const skeletonClass = this.skeleton ? "cds--skeleton" : "";
74+
75+
return `cds--tag cds--tag--filter cds--tag--${this.type} ${disabledClass} ${sizeClass} ${skeletonClass} ${this.class}`;
6476
}
6577

6678
@HostBinding("attr.aria-label") get attrAriaLabel() {

src/tag/tag-icon.directive.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Directive, HostBinding } from "@angular/core";
2+
3+
@Directive({
4+
selector: "[cdsTagIcon], [ibmTagIcon]"
5+
})
6+
export class TagIconDirective {
7+
@HostBinding("class.cds--tag__custom-icon") tagIcon = true;
8+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
HostBinding,
5+
Input
6+
} from "@angular/core";
7+
import { Tag } from "./tag.component";
8+
9+
@Component({
10+
selector: "cds-tag-operational, ibm-tag-operational",
11+
template: `
12+
<ng-container *ngIf="!skeleton">
13+
<ng-content select="[cdsTagIcon],[ibmTagIcon]"></ng-content>
14+
<span class="cds--tag__label">
15+
<ng-content></ng-content>
16+
</span>
17+
</ng-container>
18+
`,
19+
changeDetection: ChangeDetectionStrategy.OnPush
20+
})
21+
export class TagOperationalComponent extends Tag {
22+
@HostBinding("attr.role") role = "button";
23+
@HostBinding("attr.type") buttonType = "button";
24+
@HostBinding("attr.tabindex") tabIndex = 0;
25+
26+
@Input() disabled = false;
27+
28+
/**
29+
* @todo
30+
* Remove `cds--tag--${this.size}` in v7
31+
*/
32+
@HostBinding("attr.class") get attrClass() {
33+
const disabledClass = this.disabled ? "cds--tag--disabled" : "";
34+
const sizeClass = `cds--tag--${this.size} cds--layout--size-${this.size}`;
35+
const skeletonClass = this.skeleton ? "cds--skeleton" : "";
36+
37+
return `cds--tag cds--tag--operational cds--tag--${this.type} ${disabledClass} ${sizeClass} ${skeletonClass} ${this.class}`;
38+
}
39+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
EventEmitter,
5+
HostBinding,
6+
HostListener,
7+
Input,
8+
Output
9+
} from "@angular/core";
10+
11+
@Component({
12+
selector: "cds-tag-selectable, ibm-tag-selectable",
13+
template: `
14+
<ng-container *ngIf="!skeleton">
15+
<ng-content select="[cdsTagIcon],[ibmTagIcon]"></ng-content>
16+
<span class="cds--tag__label">
17+
<ng-content></ng-content>
18+
</span>
19+
</ng-container>
20+
`,
21+
changeDetection: ChangeDetectionStrategy.OnPush
22+
})
23+
export class TagSelectableComponent {
24+
@HostBinding("attr.role") role = "button";
25+
@HostBinding("attr.type") buttonType = "button";
26+
@HostBinding("attr.tabindex") tabIndex = 0;
27+
@HostBinding("attr.aria-pressed") get ariaPressed() {
28+
return this.selected;
29+
}
30+
31+
@Input() size: "sm" | "md" | "lg" = "md";
32+
@Input() skeleton = false;
33+
@Input() disabled = false;
34+
@Input() class = "";
35+
@Input() selected = false;
36+
37+
@Output() selectedChange = new EventEmitter<boolean>();
38+
39+
@HostListener("click")
40+
onClick() {
41+
this.selected = !this.selected;
42+
this.selectedChange.emit(this.selected);
43+
}
44+
45+
/**
46+
* @todo
47+
* Remove `cds--tag--${this.size}` in v7
48+
*/
49+
@HostBinding("attr.class") get attrClass() {
50+
const disabledClass = this.disabled ? "cds--tag--disabled" : "";
51+
const sizeClass = `cds--tag--${this.size} cds--layout--size-${this.size}`;
52+
const skeletonClass = this.skeleton ? "cds--skeleton" : "";
53+
const selectedClass = this.selected ? "cds--tag--selectable-selected" : "";
54+
55+
return `cds--tag cds--tag--selectable ${selectedClass} ${disabledClass} ${sizeClass} ${skeletonClass} ${this.class}`;
56+
}
57+
}

src/tag/tag.component.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ export type TagType = "red" |
3131
*/
3232
@Component({
3333
selector: "cds-tag, ibm-tag",
34-
template: `<ng-content></ng-content>`
34+
template: `
35+
<ng-container *ngIf="!skeleton">
36+
<ng-content select="[cdsTagIcon],[ibmTagIcon]"></ng-content>
37+
<span class="cds--tag__label">
38+
<ng-content></ng-content>
39+
</span>
40+
</ng-container>
41+
`
3542
})
3643
export class Tag {
3744
/**
@@ -42,11 +49,20 @@ export class Tag {
4249
/**
4350
* Tag render size
4451
*/
45-
@Input() size: "sm" | "md" = "md";
52+
@Input() size: "sm" | "md" | "lg" = "md";
4653

4754
@Input() class = "";
4855

56+
@Input() skeleton = false;
57+
58+
/**
59+
* @todo
60+
* Remove `cds--tag--${this.size}` in v7
61+
*/
4962
@HostBinding("attr.class") get attrClass() {
50-
return `cds--tag cds--tag--${this.type} cds--tag--${this.size} cds--layout--size-${this.size} ${this.class}`;
63+
const skeletonClass = this.skeleton ? "cds--skeleton" : "";
64+
const sizeClass = `cds--tag--${this.size} cds--layout--size-${this.size}`;
65+
66+
return `cds--tag cds--tag--${this.type} ${sizeClass} ${skeletonClass} ${this.class}`;
5167
}
5268
}

src/tag/tag.module.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,25 @@ import { CommonModule } from "@angular/common";
44
import { Tag } from "./tag.component";
55
import { TagFilter } from "./tag-filter.component";
66
import { IconModule } from "carbon-components-angular/icon";
7+
import { TagIconDirective } from "./tag-icon.directive";
8+
import { TagSelectableComponent } from "./tag-selectable.component";
9+
import { TagOperationalComponent } from "./tag-operational.component";
710

811
@NgModule({
9-
declarations: [ Tag, TagFilter ],
10-
exports: [ Tag, TagFilter ],
11-
imports: [ CommonModule, IconModule ]
12+
declarations: [
13+
Tag,
14+
TagFilter,
15+
TagIconDirective,
16+
TagSelectableComponent,
17+
TagOperationalComponent
18+
],
19+
exports: [
20+
Tag,
21+
TagFilter,
22+
TagIconDirective,
23+
TagSelectableComponent,
24+
TagOperationalComponent
25+
],
26+
imports: [CommonModule, IconModule]
1227
})
13-
export class TagModule { }
28+
export class TagModule {}

0 commit comments

Comments
 (0)