diff --git a/src/checkbox/checkbox-exported-tests.ts b/src/checkbox/checkbox-exported-tests.ts index 4965203141..dc0300e350 100644 --- a/src/checkbox/checkbox-exported-tests.ts +++ b/src/checkbox/checkbox-exported-tests.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import merge from "lodash/merge"; +import merge from "lodash-es/merge"; import ComponentTests from "../exported-tests/component-tests"; const defaults = { diff --git a/src/combobox/combobox.component.ts b/src/combobox/combobox.component.ts index ced6891e6f..fd65a9c3a5 100644 --- a/src/combobox/combobox.component.ts +++ b/src/combobox/combobox.component.ts @@ -263,8 +263,8 @@ export class ComboBox implements OnChanges, AfterViewInit, AfterContentInit, OnD return this._clearSelectionAria.value; } static comboBoxCount = 0; - @Input() id = `dropdown-${ComboBox.comboBoxCount++}`; - @Input() labelId = `dropdown-label-${ComboBox.comboBoxCount++}`; + @Input() id = `combobox-${ComboBox.comboBoxCount++}`; + @Input() labelId = `combobox-label-${ComboBox.comboBoxCount++}`; /** * List of items to fill the content with. * diff --git a/src/input/text-area.directive.ts b/src/input/text-area.directive.ts index 1a8223b4a5..06b473cabb 100644 --- a/src/input/text-area.directive.ts +++ b/src/input/text-area.directive.ts @@ -27,4 +27,8 @@ export class TextArea { @HostBinding("class.cds--text-area--light") get isLightTheme() { return this.theme === "light"; } + + @HostBinding("attr.data-invalid") get getInvalidAttr() { + return this.invalid ? true : undefined; + } } diff --git a/src/input/textarea-label.component.ts b/src/input/textarea-label.component.ts index 7f581c06df..3221fe07c3 100644 --- a/src/input/textarea-label.component.ts +++ b/src/input/textarea-label.component.ts @@ -31,58 +31,89 @@ import { TextArea } from "./text-area.directive"; @Component({ selector: "cds-textarea-label, ibm-textarea-label", template: ` - -
- - - - - - - - -
-
- {{helperText}} - -
-
- {{invalidText}} - -
-
- {{warnText}} - -
+ + +
+
+ +
+ +
+
+ + + + + + + + + + +
+
+ {{invalidText}} + + + +
+
+ {{warnText}} + + + +
+
+
+ +
+ {{helperText}} + +
+
+ {{invalidText}} + +
+
+ {{warnText}} + +
+
+
` }) export class TextareaLabelComponent implements AfterViewInit { @@ -136,6 +167,11 @@ export class TextareaLabelComponent implements AfterViewInit { */ @Input() ariaLabel: string; + /** + * Experimental: enable fluid state + */ + @Input() fluid = false; + // @ts-ignore @ViewChild("wrapper", { static: false }) wrapper: ElementRef; @@ -148,6 +184,14 @@ export class TextareaLabelComponent implements AfterViewInit { return this.wrapper?.nativeElement.querySelector("textarea")?.readOnly ?? false; } + @HostBinding("class.cds--text-area--fluid") get fluidClass() { + return this.fluid && !this.skeleton; + } + + @HostBinding("class.cds--text-area--fluid__skeleton") get fluidSkeletonClass() { + return this.fluid && this.skeleton; + } + /** * Creates an instance of Label. */ diff --git a/src/input/textarea.stories.ts b/src/input/textarea.stories.ts index 00ec0cd6a8..a90ebf2762 100644 --- a/src/input/textarea.stories.ts +++ b/src/input/textarea.stories.ts @@ -1,6 +1,6 @@ /* tslint:disable variable-name */ -import { moduleMetadata, Meta } from "@storybook/angular"; +import { Meta, moduleMetadata } from "@storybook/angular"; import { InputModule, TextareaLabelComponent } from "./"; export default { @@ -10,6 +10,33 @@ export default { imports: [InputModule] }) ], + args: { + disabled: false, + invalid: false, + invalidText: "Invalid entry", + warn: false, + warnText: "This is a warning!", + label: "Text input label", + helperText: "Optional helper text", + placeholder: "Placeholder", + cols: 50, + rows: 4, + autocomplete: "on", + theme: "dark", + readonly: false, + fluid: false, + skeleton: false + }, + argTypes: { + autocomplete: { + options: ["on", "off"], + control: "radio" + }, + theme: { + options: ["light", "dark"], + control: "radio" + } + }, component: TextareaLabelComponent } as Meta; @@ -21,6 +48,8 @@ const Template = (args) => ({ [invalid]="invalid" [disabled]="disabled" [invalidText]="invalidText" + [fluid]="fluid" + [skeleton]="skeleton" [warn]="warn" [warnText]="warnText"> {{label}} @@ -38,30 +67,10 @@ const Template = (args) => ({ ` }); export const Basic = Template.bind({}); -Basic.args = { - disabled: false, - invalid: false, - invalidText: "Invalid entry", - warn: false, - warnText: "This is a warning!", - label: "Text input label", - helperText: "Optional helper text", - placeholder: "Placeholder", - cols: 50, - rows: 4, - autocomplete: "on", - theme: "dark", - readonly: false -}; -Basic.argTypes = { - autocomplete: { - options: ["on", "off"], - control: "radio" - }, - theme: { - options: ["light", "dark"], - control: "radio" - } + +export const Fluid = Template.bind({}); +Fluid.args = { + fluid: true }; const SkeletonTemplate = (args) => ({ diff --git a/src/pagination/pagination.component.spec.ts b/src/pagination/pagination.component.spec.ts index abffb6cc06..c683fa256a 100644 --- a/src/pagination/pagination.component.spec.ts +++ b/src/pagination/pagination.component.spec.ts @@ -1,4 +1,4 @@ -import { TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { FormsModule } from "@angular/forms"; import { Component, OnInit } from "@angular/core"; @@ -150,4 +150,28 @@ describe("Pagination", () => { expect(buttonBackward.disabled).toBe(true); expect(element.componentInstance.currentPage).toBe(5); }); + + /** + * Number of pages should always be 1 even if totalDataLength is greater than 0 + */ + it("should recalculate pages when changing data", () => { + const fixture = TestBed.createComponent(Pagination); + const wrapper = fixture.componentInstance; + const model = new PaginationModel(); + model.currentPage = 1; + model.pageLength = 5; + model.totalDataLength = 9; + wrapper.model = model; + fixture.detectChanges(); + expect(wrapper.pageOptions).toEqual(Array(2)); + model.totalDataLength = 2; + fixture.detectChanges(); + expect(wrapper.pageOptions).toEqual(Array(1)); + model.totalDataLength = 20; + fixture.detectChanges(); + expect(wrapper.pageOptions).toEqual(Array(4)); + model.totalDataLength = 0; + fixture.detectChanges(); + expect(wrapper.pageOptions).toEqual(Array(1)); + }); }); diff --git a/src/pagination/pagination.component.ts b/src/pagination/pagination.component.ts index 1ba4a535c6..0308baa8cc 100644 --- a/src/pagination/pagination.component.ts +++ b/src/pagination/pagination.component.ts @@ -320,8 +320,14 @@ export class Pagination { } get pageOptions() { - if (this.totalDataLength && this._pageOptions.length !== this.totalDataLength) { - this._pageOptions = Array(Math.ceil(this.totalDataLength / this.itemsPerPage)); + /** + * Calculate number of pages based on totalDataLength and itemsPerPage. + * Even if totalDataLength is 0, numberOfPages should be always at least 1. + * New array will be constructed only if number of pages changes. + */ + const numberOfPages = Math.max(Math.ceil(this.totalDataLength / this.itemsPerPage), 1); + if (this._pageOptions.length !== numberOfPages) { + this._pageOptions = Array(numberOfPages); } return this._pageOptions; } diff --git a/src/search/search.component.html b/src/search/search.component.html index 1daacfb0b4..97ba0bf06b 100644 --- a/src/search/search.component.html +++ b/src/search/search.component.html @@ -5,21 +5,28 @@ 'cds--search--md': size === 'md', 'cds--search--lg': size === 'lg', 'cds--search--light': theme === 'light', - 'cds--skeleton': skeleton, + 'cds--skeleton': skeleton && !fluid, 'cds--search--expandable': expandable && !tableSearch, 'cds--search--expanded': expandable && !tableSearch && active, 'cds--toolbar-search': toolbar && !expandable, 'cds--toolbar-search--active': toolbar && !expandable && active, 'cds--toolbar-search-container-persistent': tableSearch && !expandable, 'cds--toolbar-search-container-expandable': tableSearch && expandable, - 'cds--toolbar-search-container-active': tableSearch && expandable && active + 'cds--toolbar-search-container-active': tableSearch && expandable && active, + 'cds--search--fluid': fluid, + 'cds--search--disabled': disabled }" role="search" [attr.aria-label]="ariaLabel" (click)="openSearch()"> - + -
+
({ [size]="size" (valueChange)="valueChange($event)" (clear)="clear()" + [fluid]="fluid" + [skeleton]="skeleton" [expandable]="expandable"> ` }); export const Basic = Template.bind({}); + +export const Fluid = Template.bind({}); +Fluid.args = { + fluid: true +}; diff --git a/src/select/select.component.ts b/src/select/select.component.ts index 687fcb34e3..ef2b67fb46 100644 --- a/src/select/select.component.ts +++ b/src/select/select.component.ts @@ -32,11 +32,23 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; @Component({ selector: "cds-select, ibm-select", template: ` -
- +
+
- +
+ +
+
+
+
+
+
+
+ (keydown)="onKeyDown($event)" + (focus)="fluid ? handleFocus($event) : null" + (blur)="fluid ? handleFocus($event) : null"> + +
+ +
+ {{warnText}} + +
+
- -
- {{warnText}} - -
+ + +
+ {{warnText}} + +
+
`, providers: [ @@ -209,10 +239,17 @@ export class Select implements ControlValueAccessor, AfterViewInit { @Input() theme: "light" | "dark" = "dark"; @Input() ariaLabel: string; + /** + * Experimental: enable fluid state + */ + @Input() fluid = false; + @Output() valueChange = new EventEmitter(); @ViewChild("select") select: ElementRef; + focused = false; + protected _value; ngAfterViewInit() { @@ -296,9 +333,14 @@ export class Select implements ControlValueAccessor, AfterViewInit { } } + handleFocus(event: FocusEvent) { + this.focused = event.type === "focus"; + } + /** * placeholder declarations. Replaced by the functions provided to `registerOnChange` and `registerOnTouched` */ protected onChangeHandler = (_: any) => { }; protected onTouchedHandler = () => { }; + } diff --git a/src/select/select.stories.ts b/src/select/select.stories.ts index eee63f5ac2..4d33f07381 100644 --- a/src/select/select.stories.ts +++ b/src/select/select.stories.ts @@ -35,7 +35,8 @@ export default { helperText: "Optional helper text", size: "md", theme: "dark", - display: "default" + display: "default", + fluid: false }, argTypes: { size: { @@ -74,6 +75,7 @@ const Template = (args) => ({ [helperText]="helperText" [theme]="theme" [(ngModel)]="model" + [fluid]="fluid" [display]="display"> @@ -90,6 +92,11 @@ const Template = (args) => ({ }); export const Basic = Template.bind({}); +export const Fluid = Template.bind({}); +Fluid.args = { + fluid: true +}; + const NgModelTemplate = (args) => ({ props: args, template: ` @@ -107,6 +114,7 @@ const NgModelTemplate = (args) => ({ [helperText]="helperText" [theme]="theme" [(ngModel)]="model" + [fluid]="fluid" [display]="display" ariaLabel='ngModel select'> diff --git a/src/ui-shell/sidenav/routerlink-extended.directive.ts b/src/ui-shell/sidenav/routerlink-extended.directive.ts index bdcc9151d7..ee44141203 100644 --- a/src/ui-shell/sidenav/routerlink-extended.directive.ts +++ b/src/ui-shell/sidenav/routerlink-extended.directive.ts @@ -1,6 +1,6 @@ import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core"; import { NavigationExtras, RouterLinkWithHref } from "@angular/router"; -import keys from "lodash/keys"; +import keys from "lodash-es/keys"; @Directive({ // tslint:disable-next-line