Skip to content

Commit 5887e5e

Browse files
authored
Merge pull request #524 from youda97/select-validation
Add invalid and disabled to form components
2 parents e4f91d0 + ef44672 commit 5887e5e

17 files changed

+249
-86
lines changed

src/datepicker-input/datepicker-input.component.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import {
22
Component,
33
Input,
44
Output,
5-
EventEmitter
5+
EventEmitter,
6+
TemplateRef
67
} from "@angular/core";
78

89
@Component({
@@ -37,11 +38,12 @@ import {
3738
data-date-picker-input
3839
[attr.data-input] = "type == 'single' || type == 'range' ? '' : null"
3940
[id]= "id"
40-
[attr.disabled]="(disabled ? '' : null)"
41-
[attr.data-invalid]="(invalid ? '' : null)"
41+
[disabled]="disabled"
42+
[attr.data-invalid]="(invalid ? true : null)"
4243
(change) = "valueChange.emit(dateInput.value)"/>
4344
<div *ngIf="invalid" class="bx--form-requirement">
44-
{{invalidText}}
45+
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
46+
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
4547
</div>
4648
</div>
4749
<ibm-icon-calendar16
@@ -81,7 +83,11 @@ export class DatePickerInput {
8183

8284
@Input() invalid = false;
8385

84-
@Input() invalidText: string;
86+
@Input() invalidText: string | TemplateRef<any>;
8587

8688
@Input() skeleton = false;
89+
90+
protected isTemplate(value) {
91+
return value instanceof TemplateRef;
92+
}
8793
}

src/datepicker/datepicker.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
EventEmitter,
66
ViewEncapsulation,
77
ElementRef,
8-
OnDestroy
8+
OnDestroy,
9+
TemplateRef
910
} from "@angular/core";
1011
import { FlatpickrOptions } from "ng2-flatpickr";
1112
import flatpickr from "flatpickr";
@@ -115,7 +116,7 @@ export class DatePicker implements OnDestroy {
115116

116117
@Input() invalid = false;
117118

118-
@Input() invalidText: string;
119+
@Input() invalidText: string | TemplateRef<any>;
119120

120121
@Input() skeleton = false;
121122

src/i18n/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@
104104
"OPEN_LIST_OF_OPTIONS": "Open list of options",
105105
"BACKWARD": "Backward",
106106
"FORWARD": "Forward",
107+
"TOTAL_ITEMS_UNKNOWN": "{{start}}-{{end}} items",
107108
"TOTAL_ITEMS": "{{start}}-{{end}} of {{total}} items",
108109
"TOTAL_PAGES": "{{current}} of {{last}} pages",
110+
"PAGE": "page",
109111
"OF_LAST_PAGES": "of {{last}} pages"
110112
},
111113
"TABLE": {

src/input/input.directive.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class TextInput {
2121
@Input() theme: "light" | "dark" = "dark";
2222

2323
@HostBinding("class.bx--text-input") inputClass = true;
24+
@HostBinding("class.bx--text-input--invalid") @Input() invalid = false;
2425
@HostBinding("class.bx--skeleton") @Input() skeleton = false;
2526
@HostBinding("class.bx--text-input--light") get isLightTheme() {
2627
return this.theme === "light";

src/input/input.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CommonModule } from "@angular/common";
77
import { Label } from "./label.component";
88
import { TextInput } from "./input.directive";
99
import { TextArea } from "./text-area.directive";
10+
import { WarningFilled16Module } from "@carbon/icons-angular/lib/warning--filled/16";
1011

1112
@NgModule({
1213
declarations: [
@@ -21,7 +22,8 @@ import { TextArea } from "./text-area.directive";
2122
],
2223
imports: [
2324
CommonModule,
24-
FormsModule
25+
FormsModule,
26+
WarningFilled16Module
2527
]
2628
})
2729
class InputModule { }

src/input/input.stories.ts

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { storiesOf, moduleMetadata } from "@storybook/angular";
22
import { action } from "@storybook/addon-actions";
3-
import { withKnobs, select } from "@storybook/addon-knobs/angular";
3+
import {
4+
withKnobs,
5+
text,
6+
boolean,
7+
select,
8+
number
9+
} from "@storybook/addon-knobs/angular";
410

511
import { InputModule, DocumentationModule } from "../";
612

@@ -12,26 +18,57 @@ storiesOf("Input", module).addDecorator(
1218
.addDecorator(withKnobs)
1319
.add("Label", () => ({
1420
template: `
15-
<ibm-label>
16-
Some Title
17-
<input ibmText placeholder="Optional placeholder text">
21+
<ibm-label
22+
[helperText]="helperText"
23+
[invalid]="invalid"
24+
[invalidText]="invalidText">
25+
{{label}}
26+
<input
27+
ibmText
28+
[invalid]="invalid"
29+
[disabled]="disabled"
30+
[theme]="theme"
31+
[placeholder]="placeholder">
1832
</ibm-label>
19-
`
20-
}))
21-
.add("Input", () => ({
22-
template: `
23-
<input ibmText [theme]="theme" aria-label="input" placeholder="Optional placeholder text"/>
2433
`,
2534
props: {
26-
theme: select("Theme", ["dark", "light"], "dark")
35+
theme: select("Theme", ["dark", "light"], "dark"),
36+
disabled: boolean("Disabled", false),
37+
invalid: boolean("Show form validation", false),
38+
invalidText: text("Form validation content", "Validation message here"),
39+
label: text("Label", "Text Input label"),
40+
helperText: text("Helper text", "Optional helper text."),
41+
placeholder: text("Placeholder text", "Placeholder text")
2742
}
2843
}))
2944
.add("TextArea", () => ({
3045
template: `
31-
<textarea ibmTextArea [theme]="theme" aria-label="textarea" placeholder="Optional placeholder text" rows="4" cols="50"></textarea>
46+
<ibm-label
47+
[helperText]="helperText"
48+
[invalid]="invalid"
49+
[invalidText]="invalidText">
50+
{{label}}
51+
<textarea
52+
ibmTextArea
53+
[placeholder]="placeholder"
54+
[invalid]="invalid"
55+
[disabled]="disabled"
56+
[theme]="theme"
57+
[rows]="rows"
58+
[cols]="cols"
59+
aria-label="textarea"></textarea>
60+
</ibm-label>
3261
`,
3362
props: {
34-
theme: select("Theme", ["dark", "light"], "dark")
63+
theme: select("Theme", ["dark", "light"], "dark"),
64+
disabled: boolean("Disabled", false),
65+
invalid: boolean("Show form validation", false),
66+
invalidText: text("Form validation content", "Validation message here"),
67+
label: text("Label", "Text area label"),
68+
helperText: text("Helper text", "Optional helper text."),
69+
placeholder: text("Placeholder text", "Placeholder text"),
70+
cols: number("cols", 50),
71+
rows: number("rows", 4)
3572
}
3673
}))
3774
.add("Skeleton", () => ({

src/input/label.component.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
Input,
44
AfterContentInit,
55
ElementRef,
6-
HostBinding
6+
HostBinding,
7+
TemplateRef
78
} from "@angular/core";
89

910
/**
@@ -43,7 +44,18 @@ import {
4344
}">
4445
<ng-content></ng-content>
4546
</label>
46-
<ng-content select="input,textarea,div"></ng-content>
47+
<div *ngIf="!skeleton" class="bx--form__helper-text">{{helperText}}</div>
48+
<div class="bx--text-input__field-wrapper" [attr.data-invalid]="(invalid ? true : null)">
49+
<ibm-icon-warning-filled16
50+
*ngIf="invalid"
51+
class="bx--text-input__invalid-icon bx--text-area__invalid-icon">
52+
</ibm-icon-warning-filled16>
53+
<ng-content select="input,textarea,div"></ng-content>
54+
</div>
55+
<div *ngIf="invalid" class="bx--form-requirement">
56+
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
57+
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
58+
</div>
4759
`
4860
})
4961
export class Label implements AfterContentInit {
@@ -70,6 +82,18 @@ export class Label implements AfterContentInit {
7082
* Set to `true` for a loading label.
7183
*/
7284
@Input() skeleton = false;
85+
/**
86+
* Optional helper text that appears under the label.
87+
*/
88+
@Input() helperText: string;
89+
/**
90+
* Sets the invalid text.
91+
*/
92+
@Input() invalidText: string | TemplateRef<any>;
93+
/**
94+
* Set to `true` for an invalid label component.
95+
*/
96+
@Input() invalid = false;
7397

7498
@HostBinding("class.bx--form-item") labelClass = true;
7599

@@ -89,4 +113,8 @@ export class Label implements AfterContentInit {
89113
ngAfterContentInit() {
90114
this.elementRef.nativeElement.querySelector("input,textarea,div").setAttribute("id", this.labelInputID);
91115
}
116+
117+
protected isTemplate(value) {
118+
return value instanceof TemplateRef;
119+
}
92120
}

src/input/text-area.directive.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class TextArea {
2121
@Input() theme: "light" | "dark" = "dark";
2222

2323
@HostBinding("class.bx--text-area") baseClass = true;
24+
@HostBinding("class.bx--text-area--invalid") @Input() invalid = false;
2425
@HostBinding("class.bx--skeleton") @Input() skeleton = false;
2526
@HostBinding("class.bx--text-area--light") get isLightTheme() {
2627
return this.theme === "light";

src/number-input/number.component.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Number } from "./number.component";
55
import { FormsModule } from "@angular/forms";
66
import { CaretUp16Module } from "@carbon/icons-angular/lib/caret--up/16";
77
import { CaretDown16Module } from "@carbon/icons-angular/lib/caret--down/16";
8+
import { WarningFilled16Module } from "@carbon/icons-angular/lib/warning--filled/16";
89

910
describe("Number", () => {
1011
let component: Number;
@@ -22,7 +23,8 @@ describe("Number", () => {
2223
imports: [
2324
FormsModule,
2425
CaretUp16Module,
25-
CaretDown16Module
26+
CaretDown16Module,
27+
WarningFilled16Module
2628
],
2729
providers: []
2830
});

src/number-input/number.component.ts

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { Component, Input, HostBinding, EventEmitter, Output } from "@angular/core";
1+
import {
2+
Component,
3+
Input,
4+
HostBinding,
5+
EventEmitter,
6+
Output,
7+
TemplateRef
8+
} from "@angular/core";
29
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from "@angular/forms";
310
import { isNullOrUndefined } from "util";
411

@@ -39,43 +46,51 @@ export class NumberChange {
3946
<div *ngIf="helperText" class="bx--form__helper-text">{{helperText}}</div>
4047
<div
4148
data-numberinput
42-
[attr.data-invalid]="(invalid ? '' : null)"
49+
[attr.data-invalid]="(invalid ? true : null)"
4350
class="bx--number"
4451
[ngClass]="{
4552
'bx--number--light': theme === 'light',
4653
'bx--number--nolabel': !label,
4754
'bx--number--helpertext': helperText,
4855
'bx--skeleton' : skeleton
4956
}">
50-
<input
51-
type="number"
52-
[id]="id"
53-
[value]="value"
54-
[min]="min"
55-
[max]="max"
56-
[disabled]="disabled"
57-
[required]="required"
58-
(input)="onNumberInputChange($event)"/>
59-
<div *ngIf="!skeleton" class="bx--number__controls">
60-
<button
61-
class="bx--number__control-btn up-icon"
62-
type="button"
63-
aria-live="polite"
64-
aria-atomic="true"
65-
(click)="onIncrement()">
66-
<ibm-icon-caret-up16></ibm-icon-caret-up16>
67-
</button>
68-
<button
69-
class="bx--number__control-btn down-icon"
70-
type="button"
71-
aria-live="polite"
72-
aria-atomic="true"
73-
(click)="onDecrement()">
74-
<ibm-icon-caret-down16></ibm-icon-caret-down16>
75-
</button>
57+
<label *ngIf="!skeleton && label" [for]="id" class="bx--label">{{label}}</label>
58+
<div *ngIf="helperText" class="bx--form__helper-text">{{helperText}}</div>
59+
<div class="bx--number__input-wrapper">
60+
<input
61+
type="number"
62+
[id]="id"
63+
[value]="value"
64+
[min]="min"
65+
[max]="max"
66+
[disabled]="disabled"
67+
[required]="required"
68+
(input)="onNumberInputChange($event)"/>
69+
<ibm-icon-warning-filled16
70+
*ngIf="!skeleton && invalid"
71+
class="bx--number__invalid"
72+
style="display: inherit;">
73+
</ibm-icon-warning-filled16>
74+
<div *ngIf="!skeleton" class="bx--number__controls">
75+
<button
76+
class="bx--number__control-btn up-icon"
77+
aria-live="polite"
78+
aria-atomic="true"
79+
(click)="onIncrement()">
80+
<ibm-icon-caret-up16></ibm-icon-caret-up16>
81+
</button>
82+
<button
83+
class="bx--number__control-btn down-icon"
84+
aria-live="polite"
85+
aria-atomic="true"
86+
(click)="onDecrement()">
87+
<ibm-icon-caret-down16></ibm-icon-caret-down16>
88+
</button>
89+
</div>
7690
</div>
7791
<div *ngIf="invalid" class="bx--form-requirement">
78-
{{invalidText}}
92+
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
93+
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
7994
</div>
8095
</div>
8196
`,
@@ -142,7 +157,7 @@ export class Number implements ControlValueAccessor {
142157
/**
143158
* Sets the invalid text.
144159
*/
145-
@Input() invalidText;
160+
@Input() invalidText: string | TemplateRef<any>;
146161
/**
147162
* Emits event notifying other classes when a change in state occurs in the input.
148163
*/
@@ -181,6 +196,13 @@ export class Number implements ControlValueAccessor {
181196
this.onTouched = fn;
182197
}
183198

199+
/**
200+
* Sets the disabled state through the model
201+
*/
202+
setDisabledState(isDisabled: boolean) {
203+
this.disabled = isDisabled;
204+
}
205+
184206
/**
185207
* Called when number input is blurred. Needed to properly implement `ControlValueAccessor`.
186208
* @memberof Number
@@ -228,4 +250,8 @@ export class Number implements ControlValueAccessor {
228250
this.value = event.target.value;
229251
this.emitChangeEvent();
230252
}
253+
254+
protected isTemplate(value) {
255+
return value instanceof TemplateRef;
256+
}
231257
}

0 commit comments

Comments
 (0)