Skip to content

Commit ca9be5a

Browse files
authored
Merge pull request #2354 from Akshat55/progress-bar-v4
feat: Add progress bar component
2 parents 9bded47 + 02dff0b commit ca9be5a

File tree

9 files changed

+312
-2
lines changed

9 files changed

+312
-2
lines changed

src/content-switcher/content-switcher.stories.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ storiesOf("Components|Content Switcher", module)
1717
.addDecorator(withKnobs)
1818
.add("Basic", () => ({
1919
template: `
20-
<ibm-content-switcher
20+
<ibm-content-switcher
2121
(selected)="selected($event)"
2222
[theme]="theme"
2323
[size]="size">

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export * from "carbon-components-angular/notification";
2525
export * from "carbon-components-angular/number-input";
2626
export * from "carbon-components-angular/pagination";
2727
export * from "carbon-components-angular/placeholder";
28+
export * from "carbon-components-angular/progress-bar";
2829
export * from "carbon-components-angular/progress-indicator";
2930
export * from "carbon-components-angular/radio";
3031
export * from "carbon-components-angular/search";

src/progress-bar/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./progress-bar.component";
2+
export * from "./progress-bar.module";

src/progress-bar/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ngPackage": {
3+
"lib": {
4+
"entryFile": "index.ts"
5+
}
6+
}
7+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { TestBed, ComponentFixture } from "@angular/core/testing";
2+
import { Component, DebugElement, Input } from "@angular/core";
3+
import { By } from "@angular/platform-browser";
4+
5+
import { ProgressBar } from "./";
6+
7+
@Component({
8+
template: `
9+
<ibm-progress-bar
10+
[label]="label"
11+
[helperText]="helperText"
12+
[max]="max"
13+
[size]="size"
14+
[status]="status"
15+
[type]="type"
16+
[value]="value">
17+
</ibm-progress-bar>
18+
`
19+
})
20+
class TestProgessBarComponent {
21+
@Input() label = "Label";
22+
@Input() helperText = "helper text";
23+
@Input() max = 100;
24+
@Input() size: "small" | "big" = "big";
25+
@Input() status: "active" | "finished" | "error" = "active";
26+
@Input() type: "default" | "inline" | "indented" = "default";
27+
@Input() value: undefined | number = 100;
28+
}
29+
30+
describe("Progress bar", () => {
31+
let fixture: ComponentFixture<TestProgessBarComponent>;
32+
let component: TestProgessBarComponent;
33+
let element: DebugElement;
34+
35+
beforeEach(() => {
36+
TestBed.configureTestingModule({
37+
declarations: [TestProgessBarComponent, ProgressBar]
38+
});
39+
fixture = TestBed.createComponent(TestProgessBarComponent);
40+
component = fixture.componentInstance;
41+
fixture.detectChanges();
42+
element = fixture.debugElement.query(By.css("ibm-progress-bar"));
43+
});
44+
45+
it("should create a progress bar component", () => {
46+
expect(component).toBeTruthy();
47+
expect(element).not.toBeNull();
48+
expect(element.nativeElement.className.includes("bx--progress-bar")).toBeTruthy();
49+
});
50+
51+
it("should create an indeterminate progress bar when value is undefined & status is active", () => {
52+
component.value = undefined;
53+
component.status = "active";
54+
fixture.detectChanges();
55+
expect(element.nativeElement.className.includes("bx--progress-bar--indeterminate")).toBeTruthy();
56+
});
57+
58+
it("should set the appropriate status class", () => {
59+
component.status = "finished";
60+
fixture.detectChanges();
61+
expect(element.nativeElement.className.includes("bx--progress-bar--finished")).toBeTruthy();
62+
component.status = "error";
63+
fixture.detectChanges();
64+
expect(element.nativeElement.className.includes("bx--progress-bar--error")).toBeTruthy();
65+
});
66+
67+
it("should set the the correct size class", () => {
68+
expect(element.nativeElement.className.includes("bx--progress-bar--big")).toBeTruthy();
69+
component.size = "small";
70+
fixture.detectChanges();
71+
expect(element.nativeElement.className.includes("bx--progress-bar--small")).toBeTruthy();
72+
});
73+
});
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import {
2+
Component,
3+
HostBinding,
4+
Input,
5+
TemplateRef
6+
} from "@angular/core";
7+
8+
@Component({
9+
selector: "ibm-progress-bar",
10+
template: `
11+
<div
12+
*ngIf="label"
13+
class="bx--progress-bar__label"
14+
[id]="id">
15+
<span class="bx--progress-bar__label-text">
16+
<ng-container *ngIf="!isTemplate(label)">{{label}}</ng-container>
17+
<ng-template *ngIf="isTemplate(label)" [ngTemplateOutlet]="label"></ng-template>
18+
</span>
19+
<svg
20+
*ngIf="isFinished"
21+
fill="currentColor"
22+
ibmIcon="checkmark--filled"
23+
class="bx--progress-bar__status-icon">
24+
</svg>
25+
<svg
26+
*ngIf="isError"
27+
fill="currentColor"
28+
ibmIcon="error--filled"
29+
class="bx--progress-bar__status-icon">
30+
</svg>
31+
</div>
32+
<div
33+
class="bx--progress-bar__track"
34+
role="progressbar"
35+
[attr.aria-invalid]="isError"
36+
[attr.labelledby]="id"
37+
[attr.describedby]="helperText ? helperId: null"
38+
[attr.aria-valuemin]="!indeterminate ? 0 : null"
39+
[attr.aria-valuemax]="!indeterminate ? max : null"
40+
[attr.aria-valuenow]="!indeterminate ? value : null">
41+
<div
42+
class="bx--progress-bar__bar"
43+
[ngStyle]="{
44+
'transform': !isFinished && !isError ? percentage : null
45+
}">
46+
</div>
47+
</div>
48+
<div
49+
[id]="helperId"
50+
*ngIf="helperText"
51+
class="bx--progress-bar__helper-text">
52+
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
53+
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
54+
</div>
55+
`
56+
})
57+
export class ProgressBar {
58+
/**
59+
* Current value
60+
*/
61+
@Input() set value(num: number | undefined) {
62+
this._value = num;
63+
// Validate number
64+
if (num > this.max) {
65+
this._value = this.max;
66+
}
67+
if (num < 0) {
68+
this._value = 0;
69+
}
70+
// Set values based on current state
71+
if (this.isError) {
72+
this._value = 0;
73+
} else if (this.isFinished) {
74+
this._value = this.max;
75+
}
76+
}
77+
78+
get value() {
79+
return this._value;
80+
}
81+
82+
get percentage() {
83+
return `scaleX(${this.value / this.max})`;
84+
}
85+
// Size
86+
@HostBinding("class.bx--progress-bar--big") get bigBar() {
87+
return this.size === "big";
88+
}
89+
@HostBinding("class.bx--progress-bar--small") get smallBar() {
90+
return this.size === "small";
91+
}
92+
// Type
93+
@HostBinding("class.bx--progress-bar--default") get defaultType() {
94+
return this.type === "default";
95+
}
96+
@HostBinding("class.bx--progress-bar--indented") get indentedType() {
97+
return this.type === "indented";
98+
}
99+
@HostBinding("class.bx--progress-bar--inline") get inlineType() {
100+
return this.type === "inline";
101+
}
102+
// Status
103+
@HostBinding("class.bx--progress-bar--finished") get isFinished() {
104+
return this.status === "finished";
105+
}
106+
@HostBinding("class.bx--progress-bar--error") get isError() {
107+
return this.status === "error";
108+
}
109+
@HostBinding("class.bx--progress-bar--indeterminate") get indeterminate() {
110+
return this.value === undefined && !this.isFinished && !this.isError;
111+
}
112+
static progressBarCounter = 0;
113+
114+
@Input() id = `progress-bar-${ProgressBar.progressBarCounter++}`;
115+
helperId = `progress-bar-helper-${ProgressBar.progressBarCounter}`;
116+
/**
117+
* Description of the progress bar
118+
*/
119+
@Input() label: string | TemplateRef<any>;
120+
/**
121+
* Current progress textual representation
122+
*/
123+
@Input() helperText: string | TemplateRef<any>;
124+
/**
125+
* Maximum value
126+
*/
127+
@Input() max = 100;
128+
/**
129+
* Alignment variant of the progress bar, default is `default`
130+
*/
131+
@Input() type: "default" | "inline" | "indented" = "default";
132+
/**
133+
* Current status of the progress bar, default is `active`
134+
*/
135+
@Input() status: "active" | "finished" | "error" = "active";
136+
/**
137+
* Size of the progress bar, default is `big`
138+
*/
139+
@Input() size: "small" | "big" = "big";
140+
141+
@HostBinding("class.bx--progress-bar") defaultClass = true;
142+
private _value = undefined;
143+
144+
isTemplate(value) {
145+
return value instanceof TemplateRef;
146+
}
147+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { NgModule } from "@angular/core";
2+
import { CommonModule } from "@angular/common";
3+
4+
import { ProgressBar } from "./progress-bar.component";
5+
import { IconModule } from "carbon-components-angular/icon";
6+
7+
@NgModule({
8+
declarations: [
9+
ProgressBar
10+
],
11+
exports: [
12+
ProgressBar
13+
],
14+
imports: [
15+
CommonModule,
16+
IconModule
17+
]
18+
})
19+
export class ProgressBarModule { }
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { storiesOf, moduleMetadata } from "@storybook/angular";
2+
import { withKnobs, select, text, number } from "@storybook/addon-knobs/angular";
3+
4+
import { ProgressBarModule } from "./progress-bar.module";
5+
import { DocumentationModule } from "../documentation-component/documentation.module";
6+
7+
storiesOf("Components|Progress bar", module)
8+
.addDecorator(
9+
moduleMetadata({
10+
imports: [
11+
ProgressBarModule,
12+
DocumentationModule
13+
]
14+
})
15+
)
16+
.addDecorator(withKnobs)
17+
.add("Basic", () => ({
18+
template: `
19+
<ibm-progress-bar
20+
[label]="label"
21+
[helperText]="helperText"
22+
[max]="max"
23+
[size]="size"
24+
[status]="status"
25+
[type]="type"
26+
[value]="value">
27+
</ibm-progress-bar>
28+
`,
29+
props: {
30+
label: text("Label", "Progress bar label"),
31+
helperText: text("Helper text", "Optional helper text"),
32+
max: number("max", 100),
33+
size: select("Size", ["big", "small"], "big"),
34+
status: select("Status", ["active", "finished", "error"], "active"),
35+
type: select("Type", ["default", "inline", "indented"], "default"),
36+
value: number("Current value", 35)
37+
}
38+
}))
39+
.add("Indeterminate", () => ({
40+
template: `
41+
<ibm-progress-bar
42+
[label]="label"
43+
[helperText]="helperText"
44+
[size]="size"
45+
[status]="status"
46+
[type]="type">
47+
</ibm-progress-bar>`
48+
,
49+
props: {
50+
label: text("Label", "Progress bar label"),
51+
helperText: text("Helper text", "Optional helper text"),
52+
size: select("Size", ["big", "small"], "big"),
53+
status: select("Status", ["active", "finished", "error"], "active"),
54+
type: select("Type", ["default", "inline", "indented"], "default")
55+
}
56+
}))
57+
.add("Documentation", () => ({
58+
template: `
59+
<ibm-documentation src="documentation/classes/src_progress_bar.progressbar.html"></ibm-documentation>
60+
`
61+
}));

src/slider/slider.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ export class Slider implements AfterViewInit, ControlValueAccessor {
360360
/** Converts a given px value to a "real" value in our range */
361361
convertToValue(pxAmount) {
362362
// basic concept borrowed from carbon-components
363-
// ref: https://github.com/carbon-design-system/carbon-components/blob/43bf3abdc2f8bdaa38aa84e0f733adde1e1e8894/src/components/slider/slider.js#L147-L151
363+
// https://github.com/carbon-design-system/carbon/blob/43bf3abdc2f8bdaa38aa84e0f733adde1e1e8894/src/components/slider/slider.js#L147-L151
364364
const range = this.max - this.min;
365365
const trackWidth = this.track.nativeElement.getBoundingClientRect().width;
366366
const unrounded = pxAmount / trackWidth;

0 commit comments

Comments
 (0)