diff --git a/package-lock.json b/package-lock.json index 9fa6104791..be8be7e976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28037,7 +28037,7 @@ }, "packages/angular-workspace/nimble-angular": { "name": "@ni/nimble-angular", - "version": "28.3.3", + "version": "28.4.1", "license": "MIT", "dependencies": { "tslib": "^2.2.0" @@ -28048,12 +28048,12 @@ "@angular/forms": "^17.3.12", "@angular/localize": "^17.3.12", "@angular/router": "^17.3.12", - "@ni/nimble-components": "^32.4.1" + "@ni/nimble-components": "^32.5.1" } }, "packages/angular-workspace/spright-angular": { "name": "@ni/spright-angular", - "version": "5.1.15", + "version": "5.1.17", "license": "MIT", "dependencies": { "tslib": "^2.2.0" @@ -28061,7 +28061,7 @@ "peerDependencies": { "@angular/common": "^17.3.12", "@angular/core": "^17.3.12", - "@ni/spright-components": "^4.1.15" + "@ni/spright-components": "^4.1.17" } }, "packages/blazor-workspace": { @@ -28082,10 +28082,10 @@ }, "packages/blazor-workspace/NimbleBlazor": { "name": "@ni/nimble-blazor", - "version": "18.3.3", + "version": "18.4.1", "license": "MIT", "peerDependencies": { - "@ni/nimble-components": "^32.4.1", + "@ni/nimble-components": "^32.5.1", "@ni/nimble-tokens": "^8.4.0", "cross-env": "^7.0.3", "rimraf": "^6.0.0" @@ -28156,10 +28156,10 @@ }, "packages/blazor-workspace/SprightBlazor": { "name": "@ni/spright-blazor", - "version": "2.1.15", + "version": "2.1.17", "license": "MIT", "peerDependencies": { - "@ni/spright-components": "^4.1.15", + "@ni/spright-components": "^4.1.17", "cross-env": "^7.0.3", "rimraf": "^6.0.0" } @@ -28193,7 +28193,7 @@ }, "packages/nimble-components": { "name": "@ni/nimble-components", - "version": "32.4.1", + "version": "32.5.1", "license": "MIT", "dependencies": { "@microsoft/fast-colors": "^5.3.1", @@ -28312,12 +28312,12 @@ }, "packages/spright-components": { "name": "@ni/spright-components", - "version": "4.1.15", + "version": "4.1.17", "license": "MIT", "dependencies": { "@microsoft/fast-element": "^1.12.0", "@microsoft/fast-foundation": "^2.49.6", - "@ni/nimble-components": "^32.4.1", + "@ni/nimble-components": "^32.5.1", "tslib": "^2.2.0" }, "devDependencies": { diff --git a/packages/angular-workspace/nimble-angular/CHANGELOG.json b/packages/angular-workspace/nimble-angular/CHANGELOG.json index d40813d3c9..36b8fb1aff 100644 --- a/packages/angular-workspace/nimble-angular/CHANGELOG.json +++ b/packages/angular-workspace/nimble-angular/CHANGELOG.json @@ -1,6 +1,51 @@ { "name": "@ni/nimble-angular", "entries": [ + { + "date": "Thu, 24 Oct 2024 20:52:01 GMT", + "version": "28.4.1", + "tag": "@ni/nimble-angular_v28.4.1", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/nimble-angular", + "comment": "Bump @ni/nimble-components to v32.5.1", + "commit": "not available" + } + ] + } + }, + { + "date": "Thu, 24 Oct 2024 12:36:19 GMT", + "version": "28.4.0", + "tag": "@ni/nimble-angular_v28.4.0", + "comments": { + "minor": [ + { + "author": "26874831+atmgrifter00@users.noreply.github.com", + "package": "@ni/nimble-angular", + "commit": "868a8a67780160f0a224fbc62a27511ed97db2df", + "comment": "Angular and Blazor label provider changes for scrollable tabs." + } + ] + } + }, + { + "date": "Wed, 23 Oct 2024 19:20:53 GMT", + "version": "28.3.4", + "tag": "@ni/nimble-angular_v28.3.4", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/nimble-angular", + "comment": "Bump @ni/nimble-components to v32.5.0", + "commit": "not available" + } + ] + } + }, { "date": "Wed, 23 Oct 2024 13:33:55 GMT", "version": "28.3.3", diff --git a/packages/angular-workspace/nimble-angular/CHANGELOG.md b/packages/angular-workspace/nimble-angular/CHANGELOG.md index d1244f64f7..a37712c3e0 100644 --- a/packages/angular-workspace/nimble-angular/CHANGELOG.md +++ b/packages/angular-workspace/nimble-angular/CHANGELOG.md @@ -1,9 +1,33 @@ # Change Log - @ni/nimble-angular - + +## 28.4.1 + +Thu, 24 Oct 2024 20:52:01 GMT + +### Patches + +- Bump @ni/nimble-components to v32.5.1 + +## 28.4.0 + +Thu, 24 Oct 2024 12:36:19 GMT + +### Minor changes + +- Angular and Blazor label provider changes for scrollable tabs. ([ni/nimble@868a8a6](https://github.com/ni/nimble/commit/868a8a67780160f0a224fbc62a27511ed97db2df)) + +## 28.3.4 + +Wed, 23 Oct 2024 19:20:53 GMT + +### Patches + +- Bump @ni/nimble-components to v32.5.0 + ## 28.3.3 Wed, 23 Oct 2024 13:33:55 GMT diff --git a/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core-with-defaults.directive.ts b/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core-with-defaults.directive.ts index 69607f2442..42bedfb967 100644 --- a/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core-with-defaults.directive.ts +++ b/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core-with-defaults.directive.ts @@ -21,5 +21,7 @@ export class NimbleLabelProviderCoreWithDefaultsDirective { this.elementRef.nativeElement.filterSearch = $localize`:Nimble select - search items|:Search`; this.elementRef.nativeElement.filterNoResults = $localize`:Nimble select - no items|:No items found`; this.elementRef.nativeElement.loading = $localize`:Nimble loading - loading|:Loading…`; + this.elementRef.nativeElement.scrollBackward = $localize`:Nimble scroll backward|:Scroll backward`; + this.elementRef.nativeElement.scrollForward = $localize`:Nimble scroll forward|:Scroll forward`; } } \ No newline at end of file diff --git a/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core.directive.ts b/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core.directive.ts index eff2c01cd6..b3504c2c98 100644 --- a/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core.directive.ts +++ b/packages/angular-workspace/nimble-angular/label-provider/core/nimble-label-provider-core.directive.ts @@ -85,4 +85,20 @@ export class NimbleLabelProviderCoreDirective { @Input('loading') public set loading(value: string | undefined) { this.renderer.setProperty(this.elementRef.nativeElement, 'loading', value); } + + public get scrollBackward(): string | undefined { + return this.elementRef.nativeElement.scrollBackward; + } + + @Input('scrollBackward') public set scrollBackward(value: string | undefined) { + this.renderer.setProperty(this.elementRef.nativeElement, 'scrollBackward', value); + } + + public get scrollForward(): string | undefined { + return this.elementRef.nativeElement.scrollForward; + } + + @Input('scrollForward') public set scrollForward(value: string | undefined) { + this.renderer.setProperty(this.elementRef.nativeElement, 'scrollForward', value); + } } \ No newline at end of file diff --git a/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core-with-defaults.directive.spec.ts b/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core-with-defaults.directive.spec.ts index 759303c736..9e72b59513 100644 --- a/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core-with-defaults.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core-with-defaults.directive.spec.ts @@ -36,7 +36,9 @@ describe('Nimble LabelProviderCore withDefaults directive', () => { [computeMsgId('Information', 'Nimble popup icon - information')]: 'Translated information', [computeMsgId('Search', 'Nimble select - search items')]: 'Translated search', [computeMsgId('No items found', 'Nimble select - no items')]: 'Translated no items found', - [computeMsgId('Loading…', 'Nimble loading - loading')]: 'Translated loading' + [computeMsgId('Loading…', 'Nimble loading - loading')]: 'Translated loading', + [computeMsgId('Scroll backward', 'Nimble scroll backward')]: 'Translated scroll backward', + [computeMsgId('Scroll forward', 'Nimble scroll forward')]: 'Translated scroll forward' }); const fixture = TestBed.createComponent(TestHostComponent); const testHostComponent = fixture.componentInstance; @@ -54,5 +56,7 @@ describe('Nimble LabelProviderCore withDefaults directive', () => { expect(labelProvider.filterSearch).toBe('Translated search'); expect(labelProvider.filterNoResults).toBe('Translated no items found'); expect(labelProvider.loading).toBe('Translated loading'); + expect(labelProvider.scrollBackward).toBe('Translated scroll backward'); + expect(labelProvider.scrollForward).toBe('Translated scroll forward'); }); }); diff --git a/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core.directive.spec.ts b/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core.directive.spec.ts index 3249e7e4e4..420a973d07 100644 --- a/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/label-provider/core/tests/nimble-label-provider-core.directive.spec.ts @@ -12,6 +12,9 @@ describe('Nimble Label Provider Core', () => { const label6 = 'String 6'; const label7 = 'String 7'; const label8 = 'String 8'; + const label9 = 'String 9'; + const label10 = 'String 10'; + const label11 = 'String `11'; beforeEach(() => { TestBed.configureTestingModule({ @@ -88,6 +91,21 @@ describe('Nimble Label Provider Core', () => { expect(directive.filterNoResults).toBeUndefined(); expect(nativeElement.filterNoResults).toBeUndefined(); }); + + it('has expected defaults for loading', () => { + expect(directive.loading).toBeUndefined(); + expect(nativeElement.loading).toBeUndefined(); + }); + + it('has expected defaults for scrollBackward', () => { + expect(directive.scrollBackward).toBeUndefined(); + expect(nativeElement.scrollBackward).toBeUndefined(); + }); + + it('has expected defaults for scrollForward', () => { + expect(directive.scrollForward).toBeUndefined(); + expect(nativeElement.scrollForward).toBeUndefined(); + }); }); describe('with template string values', () => { @@ -102,6 +120,9 @@ describe('Nimble Label Provider Core', () => { popup-icon-information="${label6}" filter-search="${label7}" filter-no-results="${label8}" + loading="${label9}" + scroll-backward="${label10}" + scroll-forward="${label11}" > ` @@ -165,6 +186,21 @@ describe('Nimble Label Provider Core', () => { expect(directive.filterNoResults).toBe(label8); expect(nativeElement.filterNoResults).toBe(label8); }); + + it('will use template string values for loading', () => { + expect(directive.loading).toBe(label9); + expect(nativeElement.loading).toBe(label9); + }); + + it('will use template string values for scrollBackward', () => { + expect(directive.scrollBackward).toBe(label10); + expect(nativeElement.scrollBackward).toBe(label10); + }); + + it('will use template string values for scrollForward', () => { + expect(directive.scrollForward).toBe(label11); + expect(nativeElement.scrollForward).toBe(label11); + }); }); describe('with property bound values', () => { @@ -179,6 +215,9 @@ describe('Nimble Label Provider Core', () => { [popupIconInformation]="popupIconInformation" [filterSearch]="filterSearch" [filterNoResults]="filterNoResults" + [loading]="loading" + [scrollBackward]="scrollBackward" + [scrollForward]="scrollForward" > ` @@ -194,6 +233,9 @@ describe('Nimble Label Provider Core', () => { public popupIconInformation = label1; public filterSearch = label1; public filterNoResults = label1; + public loading = label1; + public scrollBackward = label1; + public scrollForward = label1; } let fixture: ComponentFixture; @@ -298,6 +340,39 @@ describe('Nimble Label Provider Core', () => { expect(directive.filterNoResults).toBe(label2); expect(nativeElement.filterNoResults).toBe(label2); }); + + it('can be configured with property binding for loading', () => { + expect(directive.loading).toBe(label1); + expect(nativeElement.loading).toBe(label1); + + fixture.componentInstance.loading = label2; + fixture.detectChanges(); + + expect(directive.loading).toBe(label2); + expect(nativeElement.loading).toBe(label2); + }); + + it('can be configured with property binding for scrollBackward', () => { + expect(directive.scrollBackward).toBe(label1); + expect(nativeElement.scrollBackward).toBe(label1); + + fixture.componentInstance.scrollBackward = label2; + fixture.detectChanges(); + + expect(directive.scrollBackward).toBe(label2); + expect(nativeElement.scrollBackward).toBe(label2); + }); + + it('can be configured with property binding for scrollForward', () => { + expect(directive.scrollForward).toBe(label1); + expect(nativeElement.scrollForward).toBe(label1); + + fixture.componentInstance.scrollForward = label2; + fixture.detectChanges(); + + expect(directive.scrollForward).toBe(label2); + expect(nativeElement.scrollForward).toBe(label2); + }); }); describe('with attribute bound values', () => { @@ -312,6 +387,9 @@ describe('Nimble Label Provider Core', () => { [attr.popup-icon-information]="popupIconInformation" [attr.filter-search]="filterSearch" [attr.filter-no-results]="filterNoResults" + [attr.loading]="loading" + [attr.scroll-backward]="scrollBackward" + [attr.scroll-forward]="scrollForward" > ` @@ -327,6 +405,9 @@ describe('Nimble Label Provider Core', () => { public popupIconInformation = label1; public filterSearch = label1; public filterNoResults = label1; + public loading = label1; + public scrollBackward = label1; + public scrollForward = label1; } let fixture: ComponentFixture; @@ -431,5 +512,38 @@ describe('Nimble Label Provider Core', () => { expect(directive.filterNoResults).toBe(label2); expect(nativeElement.filterNoResults).toBe(label2); }); + + it('can be configured with attribute binding for loading', () => { + expect(directive.loading).toBe(label1); + expect(nativeElement.loading).toBe(label1); + + fixture.componentInstance.loading = label2; + fixture.detectChanges(); + + expect(directive.loading).toBe(label2); + expect(nativeElement.loading).toBe(label2); + }); + + it('can be configured with attribute binding for scrollBackward', () => { + expect(directive.scrollBackward).toBe(label1); + expect(nativeElement.scrollBackward).toBe(label1); + + fixture.componentInstance.scrollBackward = label2; + fixture.detectChanges(); + + expect(directive.scrollBackward).toBe(label2); + expect(nativeElement.scrollBackward).toBe(label2); + }); + + it('can be configured with attribute binding for scrollForward', () => { + expect(directive.scrollForward).toBe(label1); + expect(nativeElement.scrollForward).toBe(label1); + + fixture.componentInstance.scrollForward = label2; + fixture.detectChanges(); + + expect(directive.scrollForward).toBe(label2); + expect(nativeElement.scrollForward).toBe(label2); + }); }); }); diff --git a/packages/angular-workspace/nimble-angular/package.json b/packages/angular-workspace/nimble-angular/package.json index 6f4e1c18ca..582cf8a27b 100644 --- a/packages/angular-workspace/nimble-angular/package.json +++ b/packages/angular-workspace/nimble-angular/package.json @@ -1,6 +1,6 @@ { "name": "@ni/nimble-angular", - "version": "28.3.3", + "version": "28.4.1", "description": "Angular components for the NI Nimble Design System", "scripts": { "invoke-publish": "npm run invoke-publish:setup && cd ../dist/nimble-angular && npm publish", @@ -32,7 +32,7 @@ "@angular/forms": "^17.3.12", "@angular/localize": "^17.3.12", "@angular/router": "^17.3.12", - "@ni/nimble-components": "^32.4.1" + "@ni/nimble-components": "^32.5.1" }, "dependencies": { "tslib": "^2.2.0" diff --git a/packages/angular-workspace/spright-angular/CHANGELOG.json b/packages/angular-workspace/spright-angular/CHANGELOG.json index b3127ac7b0..0cca312f2d 100644 --- a/packages/angular-workspace/spright-angular/CHANGELOG.json +++ b/packages/angular-workspace/spright-angular/CHANGELOG.json @@ -1,6 +1,36 @@ { "name": "@ni/spright-angular", "entries": [ + { + "date": "Thu, 24 Oct 2024 20:52:01 GMT", + "version": "5.1.17", + "tag": "@ni/spright-angular_v5.1.17", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-angular", + "comment": "Bump @ni/spright-components to v4.1.17", + "commit": "not available" + } + ] + } + }, + { + "date": "Wed, 23 Oct 2024 19:20:53 GMT", + "version": "5.1.16", + "tag": "@ni/spright-angular_v5.1.16", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-angular", + "comment": "Bump @ni/spright-components to v4.1.16", + "commit": "not available" + } + ] + } + }, { "date": "Wed, 23 Oct 2024 13:33:55 GMT", "version": "5.1.15", diff --git a/packages/angular-workspace/spright-angular/CHANGELOG.md b/packages/angular-workspace/spright-angular/CHANGELOG.md index 1a882879d1..72c380e82b 100644 --- a/packages/angular-workspace/spright-angular/CHANGELOG.md +++ b/packages/angular-workspace/spright-angular/CHANGELOG.md @@ -1,9 +1,25 @@ # Change Log - @ni/spright-angular - + +## 5.1.17 + +Thu, 24 Oct 2024 20:52:01 GMT + +### Patches + +- Bump @ni/spright-components to v4.1.17 + +## 5.1.16 + +Wed, 23 Oct 2024 19:20:53 GMT + +### Patches + +- Bump @ni/spright-components to v4.1.16 + ## 5.1.15 Wed, 23 Oct 2024 13:33:55 GMT diff --git a/packages/angular-workspace/spright-angular/package.json b/packages/angular-workspace/spright-angular/package.json index 699e126890..6380c3efe8 100644 --- a/packages/angular-workspace/spright-angular/package.json +++ b/packages/angular-workspace/spright-angular/package.json @@ -1,6 +1,6 @@ { "name": "@ni/spright-angular", - "version": "5.1.15", + "version": "5.1.17", "description": "Angular components for NI Spright", "scripts": { "invoke-publish": "npm run invoke-publish:setup && cd ../dist/spright-angular && npm publish", @@ -24,7 +24,7 @@ "peerDependencies": { "@angular/common": "^17.3.12", "@angular/core": "^17.3.12", - "@ni/spright-components": "^4.1.15" + "@ni/spright-components": "^4.1.17" }, "dependencies": { "tslib": "^2.2.0" diff --git a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json index 3d569a0546..f4ad357b80 100644 --- a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json +++ b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json @@ -1,6 +1,51 @@ { "name": "@ni/nimble-blazor", "entries": [ + { + "date": "Thu, 24 Oct 2024 20:52:01 GMT", + "version": "18.4.1", + "tag": "@ni/nimble-blazor_v18.4.1", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/nimble-blazor", + "comment": "Bump @ni/nimble-components to v32.5.1", + "commit": "not available" + } + ] + } + }, + { + "date": "Thu, 24 Oct 2024 12:36:19 GMT", + "version": "18.4.0", + "tag": "@ni/nimble-blazor_v18.4.0", + "comments": { + "minor": [ + { + "author": "26874831+atmgrifter00@users.noreply.github.com", + "package": "@ni/nimble-blazor", + "commit": "868a8a67780160f0a224fbc62a27511ed97db2df", + "comment": "Angular and Blazor label provider changes for scrollable tabs." + } + ] + } + }, + { + "date": "Wed, 23 Oct 2024 19:20:53 GMT", + "version": "18.3.4", + "tag": "@ni/nimble-blazor_v18.3.4", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/nimble-blazor", + "comment": "Bump @ni/nimble-components to v32.5.0", + "commit": "not available" + } + ] + } + }, { "date": "Wed, 23 Oct 2024 13:33:55 GMT", "version": "18.3.3", diff --git a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md index 42bdf0e32e..2c82a33737 100644 --- a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md +++ b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md @@ -1,9 +1,33 @@ # Change Log - @ni/nimble-blazor - + +## 18.4.1 + +Thu, 24 Oct 2024 20:52:01 GMT + +### Patches + +- Bump @ni/nimble-components to v32.5.1 + +## 18.4.0 + +Thu, 24 Oct 2024 12:36:19 GMT + +### Minor changes + +- Angular and Blazor label provider changes for scrollable tabs. ([ni/nimble@868a8a6](https://github.com/ni/nimble/commit/868a8a67780160f0a224fbc62a27511ed97db2df)) + +## 18.3.4 + +Wed, 23 Oct 2024 19:20:53 GMT + +### Patches + +- Bump @ni/nimble-components to v32.5.0 + ## 18.3.3 Wed, 23 Oct 2024 13:33:55 GMT diff --git a/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor b/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor index 04e4a5e134..1dbd11e0d7 100644 --- a/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor +++ b/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor @@ -9,5 +9,7 @@ filter-search="@FilterSearch" filter-no-results="@FilterNoResults" loading="@Loading" + scroll-forward="@ScrollForward" + scroll-backward="@ScrollBackward" @attributes="AdditionalAttributes"> diff --git a/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor.cs b/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor.cs index 743aa336cd..09cfe693b2 100644 --- a/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor.cs +++ b/packages/blazor-workspace/NimbleBlazor/Components/NimbleLabelProviderCore.razor.cs @@ -34,6 +34,12 @@ public partial class NimbleLabelProviderCore : ComponentBase [Parameter] public string? Loading { get; set; } + [Parameter] + public string? ScrollBackward { get; set; } + + [Parameter] + public string? ScrollForward { get; set; } + /// /// Gets or sets a collection of additional attributes that will be applied to the created element. /// diff --git a/packages/blazor-workspace/NimbleBlazor/package.json b/packages/blazor-workspace/NimbleBlazor/package.json index 09afe71200..f29ccf3b9d 100644 --- a/packages/blazor-workspace/NimbleBlazor/package.json +++ b/packages/blazor-workspace/NimbleBlazor/package.json @@ -1,6 +1,6 @@ { "name": "@ni/nimble-blazor", - "version": "18.3.3", + "version": "18.4.1", "description": "Blazor components for the NI Nimble Design System", "scripts": { "pack": "cross-env-shell dotnet pack -c Release -p:PackageVersion=$npm_package_version --output ../dist", @@ -25,7 +25,7 @@ "!*" ], "peerDependencies": { - "@ni/nimble-components": "^32.4.1", + "@ni/nimble-components": "^32.5.1", "@ni/nimble-tokens": "^8.4.0", "cross-env": "^7.0.3", "rimraf": "^6.0.0" diff --git a/packages/blazor-workspace/SprightBlazor/CHANGELOG.json b/packages/blazor-workspace/SprightBlazor/CHANGELOG.json index cfc2126144..a0652f8277 100644 --- a/packages/blazor-workspace/SprightBlazor/CHANGELOG.json +++ b/packages/blazor-workspace/SprightBlazor/CHANGELOG.json @@ -1,6 +1,36 @@ { "name": "@ni/spright-blazor", "entries": [ + { + "date": "Thu, 24 Oct 2024 20:52:01 GMT", + "version": "2.1.17", + "tag": "@ni/spright-blazor_v2.1.17", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-blazor", + "comment": "Bump @ni/spright-components to v4.1.17", + "commit": "not available" + } + ] + } + }, + { + "date": "Wed, 23 Oct 2024 19:20:53 GMT", + "version": "2.1.16", + "tag": "@ni/spright-blazor_v2.1.16", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-blazor", + "comment": "Bump @ni/spright-components to v4.1.16", + "commit": "not available" + } + ] + } + }, { "date": "Wed, 23 Oct 2024 13:33:55 GMT", "version": "2.1.15", diff --git a/packages/blazor-workspace/SprightBlazor/CHANGELOG.md b/packages/blazor-workspace/SprightBlazor/CHANGELOG.md index b81101c98c..a23e1f9c74 100644 --- a/packages/blazor-workspace/SprightBlazor/CHANGELOG.md +++ b/packages/blazor-workspace/SprightBlazor/CHANGELOG.md @@ -1,9 +1,25 @@ # Change Log - @ni/spright-blazor - + +## 2.1.17 + +Thu, 24 Oct 2024 20:52:01 GMT + +### Patches + +- Bump @ni/spright-components to v4.1.17 + +## 2.1.16 + +Wed, 23 Oct 2024 19:20:53 GMT + +### Patches + +- Bump @ni/spright-components to v4.1.16 + ## 2.1.15 Wed, 23 Oct 2024 13:33:55 GMT diff --git a/packages/blazor-workspace/SprightBlazor/package.json b/packages/blazor-workspace/SprightBlazor/package.json index 37c8b27f20..4c94d04b05 100644 --- a/packages/blazor-workspace/SprightBlazor/package.json +++ b/packages/blazor-workspace/SprightBlazor/package.json @@ -1,6 +1,6 @@ { "name": "@ni/spright-blazor", - "version": "2.1.15", + "version": "2.1.17", "description": "Blazor components for Spright", "scripts": { "pack": "cross-env-shell dotnet pack -c Release -p:PackageVersion=$npm_package_version --output ../dist", @@ -25,7 +25,7 @@ "!*" ], "peerDependencies": { - "@ni/spright-components": "^4.1.15", + "@ni/spright-components": "^4.1.17", "cross-env": "^7.0.3", "rimraf": "^6.0.0" } diff --git a/packages/blazor-workspace/Tests/NimbleBlazor.Tests/Unit/Components/NimbleLabelProviderCoreTests.cs b/packages/blazor-workspace/Tests/NimbleBlazor.Tests/Unit/Components/NimbleLabelProviderCoreTests.cs index d82acf3377..6a1f0d5242 100644 --- a/packages/blazor-workspace/Tests/NimbleBlazor.Tests/Unit/Components/NimbleLabelProviderCoreTests.cs +++ b/packages/blazor-workspace/Tests/NimbleBlazor.Tests/Unit/Components/NimbleLabelProviderCoreTests.cs @@ -39,6 +39,8 @@ public void NimbleLabelProviderCore_SupportsAdditionalAttributes() [InlineData(nameof(NimbleLabelProviderCore.FilterSearch))] [InlineData(nameof(NimbleLabelProviderCore.FilterNoResults))] [InlineData(nameof(NimbleLabelProviderCore.Loading))] + [InlineData(nameof(NimbleLabelProviderCore.ScrollBackward))] + [InlineData(nameof(NimbleLabelProviderCore.ScrollForward))] public void NimbleLabelProviderCore_LabelIsSet(string propertyName) { var labelValue = propertyName + "UpdatedValue"; diff --git a/packages/nimble-components/CHANGELOG.json b/packages/nimble-components/CHANGELOG.json index 31764cf234..64f8255372 100644 --- a/packages/nimble-components/CHANGELOG.json +++ b/packages/nimble-components/CHANGELOG.json @@ -1,6 +1,36 @@ { "name": "@ni/nimble-components", "entries": [ + { + "date": "Thu, 24 Oct 2024 20:52:01 GMT", + "version": "32.5.1", + "tag": "@ni/nimble-components_v32.5.1", + "comments": { + "patch": [ + { + "author": "26874831+atmgrifter00@users.noreply.github.com", + "package": "@ni/nimble-components", + "commit": "64ede95ba2420c6ef9a30d274ec7465d22b2959d", + "comment": "Fix tabs styling." + } + ] + } + }, + { + "date": "Wed, 23 Oct 2024 19:20:53 GMT", + "version": "32.5.0", + "tag": "@ni/nimble-components_v32.5.0", + "comments": { + "minor": [ + { + "author": "26874831+atmgrifter00@users.noreply.github.com", + "package": "@ni/nimble-components", + "commit": "4173c319a64f9c600b3cfbda2b90f2b336ea5f1c", + "comment": "Scrollable tabs for Tabs and AnchorTabs." + } + ] + } + }, { "date": "Wed, 23 Oct 2024 13:33:54 GMT", "version": "32.4.1", diff --git a/packages/nimble-components/CHANGELOG.md b/packages/nimble-components/CHANGELOG.md index ad2e296e39..e75e27f308 100644 --- a/packages/nimble-components/CHANGELOG.md +++ b/packages/nimble-components/CHANGELOG.md @@ -1,9 +1,25 @@ # Change Log - @ni/nimble-components - + +## 32.5.1 + +Thu, 24 Oct 2024 20:52:01 GMT + +### Patches + +- Fix tabs styling. ([ni/nimble@64ede95](https://github.com/ni/nimble/commit/64ede95ba2420c6ef9a30d274ec7465d22b2959d)) + +## 32.5.0 + +Wed, 23 Oct 2024 19:20:53 GMT + +### Minor changes + +- Scrollable tabs for Tabs and AnchorTabs. ([ni/nimble@4173c31](https://github.com/ni/nimble/commit/4173c319a64f9c600b3cfbda2b90f2b336ea5f1c)) + ## 32.4.1 Wed, 23 Oct 2024 13:33:54 GMT diff --git a/packages/nimble-components/package.json b/packages/nimble-components/package.json index 12ea81743b..97efbdc858 100644 --- a/packages/nimble-components/package.json +++ b/packages/nimble-components/package.json @@ -1,6 +1,6 @@ { "name": "@ni/nimble-components", - "version": "32.4.1", + "version": "32.5.1", "description": "Styled web components for the NI Nimble Design System", "scripts": { "build": "npm run generate-icons && npm run generate-workers && npm run build-components && npm run bundle-components && npm run generate-scss", diff --git a/packages/nimble-components/src/anchor-tab/styles.ts b/packages/nimble-components/src/anchor-tab/styles.ts index 2cac7fdab4..2ff874da16 100644 --- a/packages/nimble-components/src/anchor-tab/styles.ts +++ b/packages/nimble-components/src/anchor-tab/styles.ts @@ -26,6 +26,7 @@ export const styles = css` align-items: center; justify-content: center; cursor: pointer; + text-wrap: nowrap; --ni-private-active-indicator-width: 2px; --ni-private-focus-indicator-width: 1px; --ni-private-indicator-lines-gap: 1px; diff --git a/packages/nimble-components/src/anchor-tabs/index.ts b/packages/nimble-components/src/anchor-tabs/index.ts index b8077de9d0..9203bf7e33 100644 --- a/packages/nimble-components/src/anchor-tabs/index.ts +++ b/packages/nimble-components/src/anchor-tabs/index.ts @@ -26,9 +26,10 @@ import { FoundationElementDefinition, FoundationElement } from '@microsoft/fast-foundation'; -import { styles } from './styles'; -import { template } from './template'; +import { styles } from '../patterns/tabs/styles'; +import { template } from '../patterns/tabs/template'; import type { AnchorTab } from '../anchor-tab'; +import type { TabsOwner } from '../patterns/tabs/types'; declare global { interface HTMLElementTagNameMap { @@ -41,7 +42,7 @@ export type TabsOptions = FoundationElementDefinition & StartEndOptions; /** * A nimble-styled set of anchor tabs */ -export class AnchorTabs extends FoundationElement { +export class AnchorTabs extends FoundationElement implements TabsOwner { /** * The id of the active tab * @@ -58,6 +59,12 @@ export class AnchorTabs extends FoundationElement { @observable public tabs!: HTMLElement[]; + /** + * @internal + */ + @observable + public showScrollButtons = false; + /** * A reference to the active tab * @public @@ -70,14 +77,44 @@ export class AnchorTabs extends FoundationElement { */ public tablist!: HTMLElement; + /** + * @internal + */ + public readonly leftScrollButton?: HTMLElement; + + /** + * @internal + */ + public readonly tabSlotName = 'anchortab'; + + private readonly tabListResizeObserver: ResizeObserver; private tabIds: string[] = []; + public constructor() { + super(); + this.tabListResizeObserver = new ResizeObserver(entries => { + let tabListVisibleWidth = entries[0]?.contentRect.width; + if (tabListVisibleWidth !== undefined) { + const buttonWidth = this.leftScrollButton?.clientWidth ?? 0; + tabListVisibleWidth = Math.ceil(tabListVisibleWidth); + if (this.showScrollButtons) { + tabListVisibleWidth += buttonWidth * 2; + } + this.showScrollButtons = tabListVisibleWidth < this.tablist.scrollWidth; + } + }); + } + /** * @internal */ public activeidChanged(_oldValue: string, _newValue: string): void { if (this.$fastController.isConnected) { this.setTabs(); + this.activetab?.scrollIntoView({ + block: 'nearest', + inline: 'start' + }); } } @@ -91,15 +128,43 @@ export class AnchorTabs extends FoundationElement { } } + /** + * @internal + */ + public onScrollLeftClick(): void { + this.tablist.scrollBy({ + left: -this.tablist.clientWidth, + behavior: 'smooth' + }); + } + + /** + * @internal + */ + public onScrollRightClick(): void { + this.tablist.scrollBy({ + left: this.tablist.clientWidth, + behavior: 'smooth' + }); + } + /** * @internal */ public override connectedCallback(): void { super.connectedCallback(); - + this.tabListResizeObserver.observe(this.tablist); this.tabIds = this.getTabIds(); } + /** + * @internal + */ + public override disconnectedCallback(): void { + super.disconnectedCallback(); + this.tabListResizeObserver.disconnect(); + } + private readonly isDisabledElement = (el: Element): el is HTMLElement => { return el.getAttribute('aria-disabled') === 'true'; }; @@ -277,6 +342,8 @@ export class AnchorTabs extends FoundationElement { tab === focusedTab ? 'true' : 'false' ); }); + + focusedTab.scrollIntoView({ block: 'nearest', inline: 'start' }); }; private getTabAnchor(tab: AnchorTab): HTMLAnchorElement { diff --git a/packages/nimble-components/src/anchor-tabs/styles.ts b/packages/nimble-components/src/anchor-tabs/styles.ts deleted file mode 100644 index c7611b7221..0000000000 --- a/packages/nimble-components/src/anchor-tabs/styles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { css } from '@microsoft/fast-element'; -import { display } from '../utilities/style/display'; - -export const styles = css` - ${display('grid')} - - :host { - grid-template-columns: auto 1fr; - grid-template-rows: auto 1fr; - } - - [part='start'] { - display: none; - } - - .tablist { - display: grid; - grid-template-rows: auto auto; - grid-template-columns: auto; - width: max-content; - align-self: end; - } -`; diff --git a/packages/nimble-components/src/anchor-tabs/template.ts b/packages/nimble-components/src/anchor-tabs/template.ts deleted file mode 100644 index 6edd59c2ac..0000000000 --- a/packages/nimble-components/src/anchor-tabs/template.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { html, ref, slotted, ViewTemplate } from '@microsoft/fast-element'; -import { - endSlotTemplate, - FoundationElementTemplate, - startSlotTemplate -} from '@microsoft/fast-foundation'; -import type { AnchorTabs, TabsOptions } from '.'; - -export const template: FoundationElementTemplate< -ViewTemplate, -TabsOptions -> = (context, definition) => html` - ${startSlotTemplate(context, definition)} -
- -
- ${endSlotTemplate(context, definition)} -`; diff --git a/packages/nimble-components/src/anchor-tabs/testing/anchor-tabs.pageobject.ts b/packages/nimble-components/src/anchor-tabs/testing/anchor-tabs.pageobject.ts new file mode 100644 index 0000000000..a8b4caade1 --- /dev/null +++ b/packages/nimble-components/src/anchor-tabs/testing/anchor-tabs.pageobject.ts @@ -0,0 +1,13 @@ +import type { AnchorTabs } from '..'; +import { anchorTabTag } from '../../anchor-tab'; +import { TabsBasePageObject } from '../../patterns/tabs/testing/tabs-base.pageobject'; + +/** + * Page object for the `nimble-anchor-tabs` component to provide consistent ways + * of querying and interacting with the component during tests. + */ +export class AnchorTabsPageObject extends TabsBasePageObject { + public constructor(tabsElement: AnchorTabs) { + super(tabsElement, anchorTabTag); + } +} diff --git a/packages/nimble-components/src/anchor-tabs/tests/anchor-tabs.spec.ts b/packages/nimble-components/src/anchor-tabs/tests/anchor-tabs.spec.ts index e8bf245730..28b32d22a3 100644 --- a/packages/nimble-components/src/anchor-tabs/tests/anchor-tabs.spec.ts +++ b/packages/nimble-components/src/anchor-tabs/tests/anchor-tabs.spec.ts @@ -14,11 +14,13 @@ import '../../anchor-tab'; import { anchorTabTag, type AnchorTab } from '../../anchor-tab'; import { waitForUpdatesAsync } from '../../testing/async-helpers'; import { fixture, Fixture } from '../../utilities/tests/fixture'; +import { AnchorTabsPageObject } from '../testing/anchor-tabs.pageobject'; describe('AnchorTabs', () => { let element: AnchorTabs; let connect: () => Promise; let disconnect: () => Promise; + let pageObject: AnchorTabsPageObject; function tab(index: number): AnchorTab { return element.children[index] as AnchorTab; @@ -42,6 +44,7 @@ describe('AnchorTabs', () => { beforeEach(async () => { ({ element, connect, disconnect } = await setup()); await connect(); + pageObject = new AnchorTabsPageObject(element); }); afterEach(async () => { @@ -118,8 +121,7 @@ describe('AnchorTabs', () => { expect(tab(0).tabIndex).toBe(-1); expect(tab(1).tabIndex).toBe(0); expect(tab(2).tabIndex).toBe(-1); - tab(0).dispatchEvent(new Event('click')); - await waitForUpdatesAsync(); + await pageObject.clickTab(0); expect(tab(0).tabIndex).toBe(0); expect(tab(1).tabIndex).toBe(-1); expect(tab(2).tabIndex).toBe(-1); @@ -130,9 +132,7 @@ describe('AnchorTabs', () => { anchor(0).addEventListener('click', () => { timesClicked += 1; }); - tab(0).dispatchEvent( - new KeyboardEvent('keydown', { key: keySpace }) - ); + await pageObject.pressKeyOnTab(0, keySpace); await waitForUpdatesAsync(); expect(timesClicked).toBe(1); }); @@ -142,9 +142,7 @@ describe('AnchorTabs', () => { anchor(0).addEventListener('click', () => { timesClicked += 1; }); - tab(0).dispatchEvent( - new KeyboardEvent('keydown', { key: keyEnter }) - ); + await pageObject.pressKeyOnTab(0, keyEnter); await waitForUpdatesAsync(); expect(timesClicked).toBe(1); }); @@ -374,4 +372,111 @@ describe('AnchorTabs', () => { expect(document.activeElement).toBe(tab(0)); }); }); + + describe('scroll buttons', () => { + async function setup(): Promise> { + return await fixture( + html`<${anchorTabsTag} activeid="tab-two"> + <${anchorTabTag}>Tab 1 + <${anchorTabTag} id="tab-two">Tab 2 + <${anchorTabTag} id="tab-three">Tab 3 + <${anchorTabTag} id="tab-four">Tab 4 + <${anchorTabTag} id="tab-five">Tab 5 + <${anchorTabTag} id="tab-six">Tab 6 + ` + ); + } + + let tabsPageObject: AnchorTabsPageObject; + + beforeEach(async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + tabsPageObject = new AnchorTabsPageObject(element); + }); + + afterEach(async () => { + await disconnect(); + }); + + it('should not show scroll buttons when the tabs fit within the container', () => { + expect(tabsPageObject.areScrollButtonsVisible()).toBeFalse(); + }); + + it('should show scroll buttons when the tabs overflow the container', async () => { + await tabsPageObject.setTabsWidth(300); + expect(tabsPageObject.areScrollButtonsVisible()).toBeTrue(); + }); + + it('should hide scroll buttons when the tabs no longer overflow the container', async () => { + await tabsPageObject.setTabsWidth(300); + await tabsPageObject.setTabsWidth(1000); + expect(tabsPageObject.areScrollButtonsVisible()).toBeFalse(); + }); + + it('should scroll left when the left scroll button is clicked', async () => { + await tabsPageObject.setTabsWidth(300); + element.activeid = 'tab-six'; // scrolls to the last tab + const currentScrollOffset = tabsPageObject.getTabsViewScrollOffset(); + await tabsPageObject.clickScrollLeftButton(); + expect(tabsPageObject.getTabsViewScrollOffset()).toBeLessThan( + currentScrollOffset + ); + }); + + it('should not scroll left when the left scroll button is clicked and the first tab is active', async () => { + await tabsPageObject.setTabsWidth(300); + await tabsPageObject.clickScrollLeftButton(); + expect(tabsPageObject.getTabsViewScrollOffset()).toBe(0); + }); + + it('should scroll right when the right scroll button is clicked', async () => { + await tabsPageObject.setTabsWidth(300); + await tabsPageObject.clickScrollRightButton(); + expect(tabsPageObject.getTabsViewScrollOffset()).toBeGreaterThan(0); + }); + + it('should not scroll right when the right scroll button is clicked and the last tab is active', async () => { + await tabsPageObject.setTabsWidth(300); + element.activeid = 'tab-six'; // scrolls to the last tab + const currentScrollOffset = tabsPageObject.getTabsViewScrollOffset(); + await tabsPageObject.clickScrollRightButton(); + expect(tabsPageObject.getTabsViewScrollOffset()).toBe( + currentScrollOffset + ); + }); + + it('should show scroll buttons when new tab is added and tabs overflow the container', async () => { + await tabsPageObject.setTabsWidth(450); + expect(tabsPageObject.areScrollButtonsVisible()).toBeFalse(); + await tabsPageObject.addTab('New Tab With Extremely Long Name'); + expect(tabsPageObject.areScrollButtonsVisible()).toBeTrue(); + }); + + it('should hide scroll buttons when tab is removed and tabs no longer overflow the container', async () => { + await tabsPageObject.setTabsWidth(500); + await tabsPageObject.addTab('New Tab With Extremely Long Name'); + expect(tabsPageObject.areScrollButtonsVisible()).toBeTrue(); + await tabsPageObject.removeTab(6); + expect(tabsPageObject.areScrollButtonsVisible()).toBeFalse(); + }); + + it('should show scroll buttons when tab label is updated and tabs overflow the container', async () => { + await tabsPageObject.setTabsWidth(450); + expect(tabsPageObject.areScrollButtonsVisible()).toBeFalse(); + await tabsPageObject.updateTabLabel( + 0, + 'New Tab With Extremely Long Name' + ); + expect(tabsPageObject.areScrollButtonsVisible()).toBeTrue(); + }); + + it('should hide scroll buttons when tab label is updated and tabs no longer overflow the container', async () => { + await tabsPageObject.setTabsWidth(550); + await tabsPageObject.addTab('New Tab With Extremely Long Name'); + expect(tabsPageObject.areScrollButtonsVisible()).toBeTrue(); + await tabsPageObject.updateTabLabel(6, 'Tab 6'); + expect(tabsPageObject.areScrollButtonsVisible()).toBeFalse(); + }); + }); }); diff --git a/packages/nimble-components/src/label-provider/core/index.ts b/packages/nimble-components/src/label-provider/core/index.ts index fcdedad25c..65c5a9667f 100644 --- a/packages/nimble-components/src/label-provider/core/index.ts +++ b/packages/nimble-components/src/label-provider/core/index.ts @@ -10,7 +10,9 @@ import { popupIconInformationLabel, filterSearchLabel, filterNoResultsLabel, - loadingLabel + loadingLabel, + scrollBackwardLabel, + scrollForwardLabel } from './label-tokens'; import { styles } from '../base/styles'; @@ -29,7 +31,9 @@ const supportedLabels = { popupIconInformation: popupIconInformationLabel, filterSearch: filterSearchLabel, filterNoResults: filterNoResultsLabel, - loading: loadingLabel + loading: loadingLabel, + scrollBackward: scrollBackwardLabel, + scrollForward: scrollForwardLabel } as const; /** @@ -65,6 +69,12 @@ export class LabelProviderCore @attr({ attribute: 'loading' }) public loading: string | undefined; + @attr({ attribute: 'scroll-backward' }) + public scrollBackward: string | undefined; + + @attr({ attribute: 'scroll-forward' }) + public scrollForward: string | undefined; + protected override readonly supportedLabels = supportedLabels; } diff --git a/packages/nimble-components/src/label-provider/core/label-token-defaults.ts b/packages/nimble-components/src/label-provider/core/label-token-defaults.ts index 1db1cd99b6..799784b176 100644 --- a/packages/nimble-components/src/label-provider/core/label-token-defaults.ts +++ b/packages/nimble-components/src/label-provider/core/label-token-defaults.ts @@ -11,5 +11,7 @@ export const coreLabelDefaults: { readonly [key in TokenName]: string } = { popupIconInformationLabel: 'Information', filterSearchLabel: 'Search', filterNoResultsLabel: 'No items found', - loadingLabel: 'Loading…' + loadingLabel: 'Loading…', + scrollBackwardLabel: 'Scroll backward', + scrollForwardLabel: 'Scroll forward' }; diff --git a/packages/nimble-components/src/label-provider/core/label-tokens.ts b/packages/nimble-components/src/label-provider/core/label-tokens.ts index 6b8f4ceec9..c55717afb8 100644 --- a/packages/nimble-components/src/label-provider/core/label-tokens.ts +++ b/packages/nimble-components/src/label-provider/core/label-tokens.ts @@ -45,3 +45,13 @@ export const loadingLabel = DesignToken.create({ name: 'loading-label', cssCustomPropertyName: null }).withDefault(coreLabelDefaults.loadingLabel); + +export const scrollBackwardLabel = DesignToken.create({ + name: 'scroll-backward-label', + cssCustomPropertyName: null +}).withDefault(coreLabelDefaults.scrollBackwardLabel); + +export const scrollForwardLabel = DesignToken.create({ + name: 'scroll-forward-label', + cssCustomPropertyName: null +}).withDefault(coreLabelDefaults.scrollForwardLabel); diff --git a/packages/nimble-components/src/patterns/tabs/styles.ts b/packages/nimble-components/src/patterns/tabs/styles.ts new file mode 100644 index 0000000000..f77de85841 --- /dev/null +++ b/packages/nimble-components/src/patterns/tabs/styles.ts @@ -0,0 +1,39 @@ +import { css } from '@microsoft/fast-element'; +import { display } from '../../utilities/style/display'; +import { smallPadding } from '../../theme-provider/design-tokens'; + +export const styles = css` + ${display('flex')} + + :host { + flex-direction: column; + } + + .tab-bar { + display: flex; + } + + [part='start'] { + display: none; + } + + .scroll-button.left { + margin-right: ${smallPadding}; + } + + .tablist { + display: flex; + width: max-content; + align-self: end; + overflow-x: scroll; + scrollbar-width: none; + } + + .scroll-button.right { + margin-left: ${smallPadding}; + } + + [part='end'] { + flex: 1; + } +`; diff --git a/packages/nimble-components/src/patterns/tabs/template.ts b/packages/nimble-components/src/patterns/tabs/template.ts new file mode 100644 index 0000000000..4719279847 --- /dev/null +++ b/packages/nimble-components/src/patterns/tabs/template.ts @@ -0,0 +1,70 @@ +import { html, ref, slotted, when } from '@microsoft/fast-element'; +import type { ViewTemplate } from '@microsoft/fast-element'; +import { + endSlotTemplate, + startSlotTemplate, + FoundationElementTemplate, + TabsOptions +} from '@microsoft/fast-foundation'; +import type { Tabs } from '../../tabs'; +import { buttonTag } from '../../button'; +import { iconArrowExpanderLeftTag } from '../../icons/arrow-expander-left'; +import { iconArrowExpanderRightTag } from '../../icons/arrow-expander-right'; +import type { TabsOwner } from './types'; +import { ButtonAppearance } from '../button/types'; +import { + scrollForwardLabel, + scrollBackwardLabel +} from '../../label-provider/core/label-tokens'; + +// prettier-ignore +export const template: FoundationElementTemplate< +ViewTemplate, +TabsOptions +> = (context, definition) => html` +
+ ${startSlotTemplate(context, definition)} + ${when(x => x.showScrollButtons, html` + <${buttonTag} + content-hidden + class="scroll-button left" + appearance="${ButtonAppearance.ghost}" + tabindex="-1" + @click="${x => x.onScrollLeftClick()}" + ${ref('leftScrollButton')} + > + ${x => scrollForwardLabel.getValueFor(x)} + <${iconArrowExpanderLeftTag} slot="start"> + + `)} +
+ + +
+ ${when(x => x.showScrollButtons, html` + <${buttonTag} + content-hidden + class="scroll-button right" + appearance="${ButtonAppearance.ghost}" + tabindex="-1" + @click="${x => x.onScrollRightClick()}" + > + ${x => scrollBackwardLabel.getValueFor(x)} + <${iconArrowExpanderRightTag} slot="start"> + + `)} + ${endSlotTemplate(context, definition)} +
+ ${when(x => 'tabpanels' in x, html` +
+ +
+ `)} +`; diff --git a/packages/nimble-components/src/patterns/tabs/testing/tabs-base.pageobject.ts b/packages/nimble-components/src/patterns/tabs/testing/tabs-base.pageobject.ts new file mode 100644 index 0000000000..854aeac718 --- /dev/null +++ b/packages/nimble-components/src/patterns/tabs/testing/tabs-base.pageobject.ts @@ -0,0 +1,116 @@ +import type { Button } from '../../../button'; +import { waitForUpdatesAsync } from '../../../testing/async-helpers'; +import { waitTimeout } from '../../../utilities/testing/component'; +import type { TabsOwner } from '../types'; + +/** + * Page object for the `nimble-tabs` and `nimble-anchor-tabs` components to provide + * consistent ways of querying and interacting with the component during tests. + */ +export abstract class TabsBasePageObject { + public constructor( + protected readonly tabsElement: T, + protected readonly tabElementName: string, + protected readonly tabPanelElementName?: string + ) {} + + public async clickTab(index: number): Promise { + if (index >= this.tabsElement.tabs.length) { + throw new Error(`Tab with index ${index} not found`); + } + this.tabsElement.tabs[index]!.click(); + await waitForUpdatesAsync(); + } + + public async pressKeyOnTab(index: number, key: string): Promise { + if (index >= this.tabsElement.tabs.length) { + throw new Error(`Tab with index ${index} not found`); + } + const tab = this.tabsElement.tabs[index]!; + tab.dispatchEvent(new KeyboardEvent('keydown', { key })); + await waitForUpdatesAsync(); + } + + public async setTabsWidth(width: number): Promise { + this.tabsElement.style.width = `${width}px`; + await waitForUpdatesAsync(); + await waitForUpdatesAsync(); // wait for the resize observer to fire + } + + public async clickScrollLeftButton(): Promise { + const leftButton = this.tabsElement.shadowRoot!.querySelector