From 2faeda0700c39ee919363d2234d492e6cf21af2e Mon Sep 17 00:00:00 2001
From: Suneeh
Date: Thu, 27 Mar 2025 22:02:20 +0100
Subject: [PATCH 01/11] feat: ActionButton and DataSource
---
projects/components/action-button/index.ts | 3 +
.../components/action-button/ng-package.json | 5 ++
.../components/action-button/public_api.ts | 2 +
.../src/action-button.component.html | 19 +++++
.../src/action-button.component.scss | 8 ++
.../src/action-button.component.ts | 28 +++++++
.../src/action-data-source.spec.ts | 46 +++++++++++
.../action-button/src/action-data-source.ts | 51 ++++++++++++
projects/components/package.json | 2 +-
.../action-button-demo.component.html | 75 ++++++++++++++++++
.../action-button-demo.component.scss | 5 ++
.../action-button-demo.component.ts | 78 +++++++++++++++++++
.../src/app/app.component.html | 1 +
.../src/app/app.config.ts | 4 +
.../app/upgrade-notes/upgrade-notes.page.html | 4 +
15 files changed, 330 insertions(+), 1 deletion(-)
create mode 100644 projects/components/action-button/index.ts
create mode 100644 projects/components/action-button/ng-package.json
create mode 100644 projects/components/action-button/public_api.ts
create mode 100644 projects/components/action-button/src/action-button.component.html
create mode 100644 projects/components/action-button/src/action-button.component.scss
create mode 100644 projects/components/action-button/src/action-button.component.ts
create mode 100644 projects/components/action-button/src/action-data-source.spec.ts
create mode 100644 projects/components/action-button/src/action-data-source.ts
create mode 100644 projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html
create mode 100644 projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.scss
create mode 100644 projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
diff --git a/projects/components/action-button/index.ts b/projects/components/action-button/index.ts
new file mode 100644
index 0000000..c74f953
--- /dev/null
+++ b/projects/components/action-button/index.ts
@@ -0,0 +1,3 @@
+// export what ./public_api exports so we can import with the lib name like this:
+// import { ModuleA } from 'libname'
+export * from './public_api';
diff --git a/projects/components/action-button/ng-package.json b/projects/components/action-button/ng-package.json
new file mode 100644
index 0000000..1dc0b0b
--- /dev/null
+++ b/projects/components/action-button/ng-package.json
@@ -0,0 +1,5 @@
+{
+ "lib": {
+ "entryFile": "index.ts"
+ }
+}
diff --git a/projects/components/action-button/public_api.ts b/projects/components/action-button/public_api.ts
new file mode 100644
index 0000000..72ce6c5
--- /dev/null
+++ b/projects/components/action-button/public_api.ts
@@ -0,0 +1,2 @@
+export { ZvActionButtonComponent, type IZvActionButton } from './src/action-button.component';
+export { ZvActionDataSource, type ZvActionDataSourceOptions } from './src/action-data-source';
diff --git a/projects/components/action-button/src/action-button.component.html b/projects/components/action-button/src/action-button.component.html
new file mode 100644
index 0000000..04aee9b
--- /dev/null
+++ b/projects/components/action-button/src/action-button.component.html
@@ -0,0 +1,19 @@
+
+
+ @if (actionDs().succeeded()) {
+ check_circle
+ }
+ @if (actionDs().hasError()) {
+ {{ actionDs().exception().errorObject | zvErrorMessage }}
+ }
+
diff --git a/projects/components/action-button/src/action-button.component.scss b/projects/components/action-button/src/action-button.component.scss
new file mode 100644
index 0000000..ff31128
--- /dev/null
+++ b/projects/components/action-button/src/action-button.component.scss
@@ -0,0 +1,8 @@
+.app-action-button__check {
+ color: green;
+ vertical-align: middle;
+}
+
+.app-action-button__error {
+ color: var(--zv-components-error);
+}
diff --git a/projects/components/action-button/src/action-button.component.ts b/projects/components/action-button/src/action-button.component.ts
new file mode 100644
index 0000000..0efec54
--- /dev/null
+++ b/projects/components/action-button/src/action-button.component.ts
@@ -0,0 +1,28 @@
+import { ChangeDetectionStrategy, Component, input, Signal } from '@angular/core';
+import { ThemePalette } from '@angular/material/core';
+import { ZvErrorMessagePipe } from '@zvoove/components/core';
+import { MatLabel } from '@angular/material/input';
+import { ZvBlockUi } from '@zvoove/components/block-ui';
+import { MatIcon } from '@angular/material/icon';
+import { MatButtonModule } from '@angular/material/button';
+import { ZvActionDataSource } from './action-data-source';
+
+export interface IZvActionButton {
+ label: string;
+ color: ThemePalette | null;
+ icon: string;
+ dataCy: string;
+ isDisabled?: Signal;
+}
+
+@Component({
+ selector: 'zv-action-button',
+ templateUrl: './action-button.component.html',
+ styleUrl: './action-button.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [MatLabel, ZvErrorMessagePipe, ZvBlockUi, MatIcon, MatButtonModule],
+})
+export class ZvActionButtonComponent {
+ public actionDs = input.required>();
+ public button = input.required();
+}
diff --git a/projects/components/action-button/src/action-data-source.spec.ts b/projects/components/action-button/src/action-data-source.spec.ts
new file mode 100644
index 0000000..b443cda
--- /dev/null
+++ b/projects/components/action-button/src/action-data-source.spec.ts
@@ -0,0 +1,46 @@
+import { fakeAsync, tick } from '@angular/core/testing';
+import { switchMap, throwError, timer } from 'rxjs';
+import { ZvActionDataSource } from './action-data-source';
+
+describe('ActionDataSource', () => {
+ it('should set properties correctly', fakeAsync(() => {
+ const dataSource = new ZvActionDataSource({ actionFn: () => timer(1) });
+ expect(dataSource.exception()).toBe(null);
+ expect(dataSource.pending()).toBe(false);
+ expect(dataSource.hasError()).toBe(false);
+ expect(dataSource.succeeded()).toBe(false);
+
+ dataSource.execute();
+ expect(dataSource.exception()).toBe(null);
+ expect(dataSource.pending()).toBe(true);
+ expect(dataSource.hasError()).toBe(false);
+ expect(dataSource.succeeded()).toBe(false);
+
+ tick(1);
+ expect(dataSource.exception()).toBe(null);
+ expect(dataSource.pending()).toBe(false);
+ expect(dataSource.hasError()).toBe(false);
+ expect(dataSource.succeeded()).toBe(true);
+ }));
+
+ it('should set error correctly', fakeAsync(() => {
+ const error = new Error('action failed');
+ const dataSource = new ZvActionDataSource({ actionFn: () => timer(1).pipe(switchMap(() => throwError(() => error))) });
+ expect(dataSource.exception()).toBe(null);
+ expect(dataSource.pending()).toBe(false);
+ expect(dataSource.hasError()).toBe(false);
+ expect(dataSource.succeeded()).toBe(false);
+
+ dataSource.execute();
+ expect(dataSource.exception()).toBe(null);
+ expect(dataSource.pending()).toBe(true);
+ expect(dataSource.hasError()).toBe(false);
+ expect(dataSource.succeeded()).toBe(false);
+
+ tick(1);
+ expect(dataSource.exception()?.errorObject).toBe(error);
+ expect(dataSource.pending()).toBe(false);
+ expect(dataSource.hasError()).toBe(true);
+ expect(dataSource.succeeded()).toBe(false);
+ }));
+});
diff --git a/projects/components/action-button/src/action-data-source.ts b/projects/components/action-button/src/action-data-source.ts
new file mode 100644
index 0000000..657ddec
--- /dev/null
+++ b/projects/components/action-button/src/action-data-source.ts
@@ -0,0 +1,51 @@
+import { signal } from '@angular/core';
+import { IZvException } from '@zvoove/components/core';
+import { Observable, Subscription } from 'rxjs';
+import { first } from 'rxjs/operators';
+
+export interface ZvActionDataSourceOptions {
+ actionFn: () => Observable;
+}
+
+export class ZvActionDataSource {
+ private _exception = signal(null);
+ private _pending = signal(false);
+ private _hasError = signal(false);
+ private _succeeded = signal(false);
+
+ private actionSub = Subscription.EMPTY;
+
+ constructor(private options: ZvActionDataSourceOptions) {}
+
+ public readonly exception = this._exception.asReadonly();
+ public readonly pending = this._pending.asReadonly();
+ public readonly hasError = this._hasError.asReadonly();
+ public readonly succeeded = this._succeeded.asReadonly();
+
+ public execute() {
+ this.actionSub.unsubscribe();
+ this._pending.set(true);
+ this._hasError.set(false);
+ this._exception.set(null);
+ this._succeeded.set(false);
+
+ let load$ = this.options.actionFn();
+ load$ = load$.pipe(first());
+
+ this.actionSub = load$.subscribe({
+ next: () => {
+ this._pending.set(false);
+ this._succeeded.set(true);
+ },
+ error: (err: unknown) => {
+ this._pending.set(false);
+ this._hasError.set(true);
+ this._exception.set({
+ errorObject: err,
+ alignCenter: true,
+ icon: 'sentiment_very_dissatisfied',
+ });
+ },
+ });
+ }
+}
diff --git a/projects/components/package.json b/projects/components/package.json
index e74c325..90eda66 100644
--- a/projects/components/package.json
+++ b/projects/components/package.json
@@ -1,7 +1,7 @@
{
"name": "@zvoove/components",
"description": "A set of angular components compatible with and/or dependent on @angular/material.",
- "version": "19.2.2",
+ "version": "19.2.3",
"license": "MIT",
"repository": {
"type": "git",
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html
new file mode 100644
index 0000000..ddd8f47
--- /dev/null
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html
@@ -0,0 +1,75 @@
+
+
+
+ Color input is currently only compatibil with for m2.
+
+
+
+
+ Loaded successfully {{ counter() }} times
+
+
+
+
+
Settings:
+ Load Error
+ Disable Button
+
+
+
Try it:
+
+ @for (value of themePalletValues; track value) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @angular/material card & icon
+
+
+
+ The zv-action-button uses the
+ @angular/material button as
+ well as the
+ @angular/material icon and the
+ @angular/material label in its
+ view. So @angular/material needs to be installed.
+
+
+
+
+
+ Imports
+
+
+ Add the following to your imports, where you want to use the zv-action-button:
+
+
+
+
+
+ Usage
+
+
+ An example of how to use the IZvActionButton:
+ TypeScript
+
+ HTML
+
+
+
+
+
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.scss b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.scss
new file mode 100644
index 0000000..ddbf136
--- /dev/null
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.scss
@@ -0,0 +1,5 @@
+.app-action-data-source-demo__demo-wrapper {
+ display: flex;
+ gap: 1em;
+ justify-content: space-between;
+}
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
new file mode 100644
index 0000000..c4d0e06
--- /dev/null
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
@@ -0,0 +1,78 @@
+import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
+import { MatCardModule } from '@angular/material/card';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { ThemePalette } from '@angular/material/core';
+import { IZvActionButton, ZvActionButtonComponent, ZvActionDataSource } from '@zvoove/components/action-button';
+import { of } from 'rxjs';
+import { delay, map } from 'rxjs/operators';
+import { allSharedImports } from '../common/shared-imports';
+
+@Component({
+ selector: 'app-action-button-demo',
+ templateUrl: './action-button-demo.component.html',
+ styleUrls: ['./action-button-demo.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [allSharedImports, MatCardModule, MatCheckboxModule, ZvActionButtonComponent],
+})
+export class ActionButtonDemoComponent {
+ public loadError = false;
+ public color: ThemePalette | null = null;
+ public readonly counter = signal(0);
+ public readonly isDisabled = signal(false);
+
+ public dataSource = new ZvActionDataSource({
+ actionFn: () => {
+ return of('foo').pipe(
+ delay(1000),
+ map((x) => {
+ if (this.loadError) {
+ throw new Error('this is the server error (loading)');
+ }
+ this.counter.update((c) => c + 1);
+ return x;
+ })
+ );
+ },
+ });
+
+ public themePalletValues: (ThemePalette | null)[] = [null, 'primary', 'accent', 'warn'];
+ updateDisabled(isChecked: boolean) {
+ this.isDisabled.set(isChecked);
+ }
+
+ getActionButton(color: ThemePalette | null): IZvActionButton {
+ return {
+ label: `I am an IZvActionButton (color: ${color})`,
+ color: color,
+ icon: 'home',
+ dataCy: 'test',
+ isDisabled: this.isDisabled,
+ };
+ }
+
+ importsCode = `
+import { IZvActionButton, ZvActionButtonComponent, ZvActionDataSource } from '@zvoove/components/action-button';
+// ...
+imports: [
+ ZvActionButtonComponent,
+],`;
+
+ usageCodeTs = `
+private http = inject(HttpClient);
+actionDataSource = new ZvActionDataSource({
+ actionFn: () => this.http.post('https://YOUR.API/POST-ROUTE', {}),
+});
+
+public actionButton: IZvActionButton = {
+ label: '',
+ color: null,
+ icon: 'home',
+ dataCy: 'testsWillFindMe',
+ isDisabled: this.isDisabled,
+};
+`;
+
+ usageCodeHtml = `
+
+`;
+}
diff --git a/projects/zvoove-components-demo/src/app/app.component.html b/projects/zvoove-components-demo/src/app/app.component.html
index ff23cfd..383ff50 100644
--- a/projects/zvoove-components-demo/src/app/app.component.html
+++ b/projects/zvoove-components-demo/src/app/app.component.html
@@ -35,6 +35,7 @@
Components
+ Action Button
Block Ui
Button
Card
diff --git a/projects/zvoove-components-demo/src/app/app.config.ts b/projects/zvoove-components-demo/src/app/app.config.ts
index 98b9ac1..a524401 100644
--- a/projects/zvoove-components-demo/src/app/app.config.ts
+++ b/projects/zvoove-components-demo/src/app/app.config.ts
@@ -27,6 +27,10 @@ export const appConfig: ApplicationConfig = {
{ provide: LOCALE_ID, useValue: getUsersLocale(['en', 'de'], 'en-GB') },
provideHttpClient(withInterceptorsFromDi(), withFetch()),
provideRouter([
+ {
+ path: 'action-button',
+ loadComponent: () => import('./action-button-demo/action-button-demo.component').then((c) => c.ActionButtonDemoComponent),
+ },
{
path: 'button',
loadComponent: () => import('./button-demo/button-demo.page').then((m) => m.ButtonDemoPage),
diff --git a/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html b/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html
index 6e7ad0c..0685592 100644
--- a/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html
+++ b/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html
@@ -1,3 +1,7 @@
+
+ action: Add ActionButton
+ action: Add ActionDatasource
+
fix: is24HourFormat not returning true for de-DE.
From c0c419b9d72092edc3b000dfeec6963f7d3b4759 Mon Sep 17 00:00:00 2001
From: Suneeh
Date: Thu, 3 Apr 2025 19:11:21 +0200
Subject: [PATCH 02/11] adds harness + tests for ActionButtonComponent
---
.../src/action-button.component.html | 4 +-
.../src/action-button.component.scss | 4 +-
.../src/action-button.component.spec.ts | 118 ++++++++++++++++++
.../src/action-button.component.ts | 10 +-
.../src/action-button.harness.ts | 36 ++++++
.../src/action-data-source.spec.ts | 2 +-
.../action-button/src/action-data-source.ts | 15 +--
.../block-ui/src/block-ui.component.spec.ts | 2 +-
8 files changed, 170 insertions(+), 21 deletions(-)
create mode 100644 projects/components/action-button/src/action-button.component.spec.ts
create mode 100644 projects/components/action-button/src/action-button.harness.ts
diff --git a/projects/components/action-button/src/action-button.component.html b/projects/components/action-button/src/action-button.component.html
index 04aee9b..0364c10 100644
--- a/projects/components/action-button/src/action-button.component.html
+++ b/projects/components/action-button/src/action-button.component.html
@@ -11,9 +11,9 @@
{{ button().label }}
@if (actionDs().succeeded()) {
- check_circle
+ check_circle
}
@if (actionDs().hasError()) {
- {{ actionDs().exception().errorObject | zvErrorMessage }}
+ {{ actionDs().exception() | zvErrorMessage }}
}
diff --git a/projects/components/action-button/src/action-button.component.scss b/projects/components/action-button/src/action-button.component.scss
index ff31128..eccb777 100644
--- a/projects/components/action-button/src/action-button.component.scss
+++ b/projects/components/action-button/src/action-button.component.scss
@@ -1,8 +1,8 @@
-.app-action-button__check {
+.zv-action-button__check {
color: green;
vertical-align: middle;
}
-.app-action-button__error {
+.zv-action-button__error {
color: var(--zv-components-error);
}
diff --git a/projects/components/action-button/src/action-button.component.spec.ts b/projects/components/action-button/src/action-button.component.spec.ts
new file mode 100644
index 0000000..5770fc2
--- /dev/null
+++ b/projects/components/action-button/src/action-button.component.spec.ts
@@ -0,0 +1,118 @@
+import { HarnessLoader } from '@angular/cdk/testing';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { switchMap, throwError, timer } from 'rxjs';
+import { IZvActionButton, ZvActionButtonComponent } from './action-button.component';
+import { ZvActionButtonHarness } from './action-button.harness';
+import { ZvActionDataSource } from './action-data-source';
+
+@Component({
+ selector: 'zv-test-component',
+ template: ``,
+ // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
+ changeDetection: ChangeDetectionStrategy.Default,
+ imports: [ZvActionButtonComponent],
+})
+export class TestComponent {
+ public readonly isDisabled = signal(false);
+ dataSource = new ZvActionDataSource({
+ actionFn: () => timer(1),
+ });
+
+ actionButton: IZvActionButton = {
+ label: `label`,
+ color: 'primary',
+ icon: 'home',
+ dataCy: 'dataCyTest',
+ isDisabled: this.isDisabled,
+ };
+}
+describe('ZvActionButton', () => {
+ let fixture: ComponentFixture;
+ let component: TestComponent;
+ let loader: HarnessLoader;
+ let harness: ZvActionButtonHarness;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [TestComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(TestComponent);
+ component = fixture.componentInstance;
+ loader = TestbedHarnessEnvironment.loader(fixture);
+ harness = await loader.getHarness(ZvActionButtonHarness);
+ });
+
+ it('should create', () => {
+ expect(component).toBeDefined();
+ });
+
+ it('should be blocked while pending', async () => {
+ const button = await harness.getButton();
+ button?.click();
+ expect(component.dataSource.pending()).toBeTrue();
+ expect(await harness.isBlocked()).toBeTrue();
+ });
+
+ it('should be disabled while pending', async () => {
+ const button = await harness.getButton();
+ button?.click();
+ expect(component.dataSource.pending()).toBeTrue();
+ expect(await button!.getProperty('disabled')).toBeTrue();
+ });
+
+ it('should respect disabled property', async () => {
+ const button = await harness.getButton();
+ expect(await button?.getProperty('disabled')).toBeFalse();
+ component.isDisabled.set(true);
+ expect(await button!.getProperty('disabled')).toBeTrue();
+ component.isDisabled.set(false);
+ expect(await button?.getProperty('disabled')).toBeFalse();
+ });
+
+ it('should respect color property', async () => {
+ const button = await harness.getButton();
+ expect(await button?.hasClass('mat-primary')).toBeTrue();
+ component.actionButton.color = 'accent';
+ component.actionButton = { ...component.actionButton, color: 'accent' };
+ expect(await button?.hasClass('mat-accent')).toBeTrue();
+ component.actionButton = { ...component.actionButton, color: 'warn' };
+ expect(await button?.hasClass('mat-warn')).toBeTrue();
+ });
+
+ it('should have dataCy attribute', async () => {
+ const button = await harness.getButton();
+ expect(await button?.getAttribute('data-cy')).toBe('dataCyTest');
+ });
+
+ it('should show icon', async () => {
+ const icon = await harness.getButtonIcon();
+ expect(await icon?.text()).toBe('home');
+ });
+
+ it('should show label', async () => {
+ const label = await harness.getButtonLabel();
+ expect(await label?.text()).toBe('label');
+ });
+
+ it('should show success icon', fakeAsync(async () => {
+ const button = await harness.getButton();
+ button?.click();
+ tick(1);
+ expect(component.dataSource.pending()).toBeFalse();
+ expect(component.dataSource.succeeded()).toBeTrue();
+ expect(await harness.showsSuccess()).toBeTrue();
+ }));
+
+ it('should show error message', fakeAsync(async () => {
+ const error = new Error('action failed');
+ component.dataSource = new ZvActionDataSource({ actionFn: () => timer(1).pipe(switchMap(() => throwError(() => error))) });
+ const button = await harness.getButton();
+ button?.click();
+ tick(1);
+ expect(await harness.showsSuccess()).toBeFalse();
+ expect(await (await harness.getError())?.text()).toBe(error.message);
+ }));
+});
diff --git a/projects/components/action-button/src/action-button.component.ts b/projects/components/action-button/src/action-button.component.ts
index 0efec54..dbc46ea 100644
--- a/projects/components/action-button/src/action-button.component.ts
+++ b/projects/components/action-button/src/action-button.component.ts
@@ -1,10 +1,10 @@
import { ChangeDetectionStrategy, Component, input, Signal } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
import { ThemePalette } from '@angular/material/core';
-import { ZvErrorMessagePipe } from '@zvoove/components/core';
+import { MatIcon } from '@angular/material/icon';
import { MatLabel } from '@angular/material/input';
import { ZvBlockUi } from '@zvoove/components/block-ui';
-import { MatIcon } from '@angular/material/icon';
-import { MatButtonModule } from '@angular/material/button';
+import { ZvErrorMessagePipe } from '@zvoove/components/core';
import { ZvActionDataSource } from './action-data-source';
export interface IZvActionButton {
@@ -23,6 +23,6 @@ export interface IZvActionButton {
imports: [MatLabel, ZvErrorMessagePipe, ZvBlockUi, MatIcon, MatButtonModule],
})
export class ZvActionButtonComponent {
- public actionDs = input.required>();
- public button = input.required();
+ public readonly actionDs = input.required>();
+ public readonly button = input.required();
}
diff --git a/projects/components/action-button/src/action-button.harness.ts b/projects/components/action-button/src/action-button.harness.ts
new file mode 100644
index 0000000..db46fb9
--- /dev/null
+++ b/projects/components/action-button/src/action-button.harness.ts
@@ -0,0 +1,36 @@
+import { ComponentHarness, TestElement } from '@angular/cdk/testing';
+
+export class ZvActionButtonHarness extends ComponentHarness {
+ static hostSelector = 'zv-action-button';
+
+ private _button = this.locatorForOptional('button');
+ private _buttonIcon = this.locatorForOptional('button mat-icon');
+ private _buttonLabel = this.locatorForOptional('button mat-label');
+ private _blockOverlay = this.locatorForOptional('.zv-block-ui__overlay');
+ private _successDiv = this.locatorForOptional('.zv-action-button__check');
+ private _errorDiv = this.locatorForOptional('.zv-action-button__error');
+
+ public async getButton(): Promise {
+ return await this._button();
+ }
+
+ public async getButtonIcon(): Promise {
+ return await this._buttonIcon();
+ }
+
+ public async getButtonLabel(): Promise {
+ return await this._buttonLabel();
+ }
+
+ public async showsSuccess(): Promise {
+ return !!(await this._successDiv());
+ }
+
+ public async getError(): Promise {
+ return await this._errorDiv();
+ }
+
+ public async isBlocked(): Promise {
+ return !!(await this._blockOverlay());
+ }
+}
diff --git a/projects/components/action-button/src/action-data-source.spec.ts b/projects/components/action-button/src/action-data-source.spec.ts
index b443cda..2d8086d 100644
--- a/projects/components/action-button/src/action-data-source.spec.ts
+++ b/projects/components/action-button/src/action-data-source.spec.ts
@@ -38,7 +38,7 @@ describe('ActionDataSource', () => {
expect(dataSource.succeeded()).toBe(false);
tick(1);
- expect(dataSource.exception()?.errorObject).toBe(error);
+ expect(dataSource.exception()).toBe(error);
expect(dataSource.pending()).toBe(false);
expect(dataSource.hasError()).toBe(true);
expect(dataSource.succeeded()).toBe(false);
diff --git a/projects/components/action-button/src/action-data-source.ts b/projects/components/action-button/src/action-data-source.ts
index 657ddec..857862e 100644
--- a/projects/components/action-button/src/action-data-source.ts
+++ b/projects/components/action-button/src/action-data-source.ts
@@ -1,5 +1,4 @@
import { signal } from '@angular/core';
-import { IZvException } from '@zvoove/components/core';
import { Observable, Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
@@ -8,10 +7,10 @@ export interface ZvActionDataSourceOptions {
}
export class ZvActionDataSource {
- private _exception = signal(null);
- private _pending = signal(false);
- private _hasError = signal(false);
- private _succeeded = signal(false);
+ private readonly _exception = signal(null);
+ private readonly _pending = signal(false);
+ private readonly _hasError = signal(false);
+ private readonly _succeeded = signal(false);
private actionSub = Subscription.EMPTY;
@@ -40,11 +39,7 @@ export class ZvActionDataSource {
error: (err: unknown) => {
this._pending.set(false);
this._hasError.set(true);
- this._exception.set({
- errorObject: err,
- alignCenter: true,
- icon: 'sentiment_very_dissatisfied',
- });
+ this._exception.set(err);
},
});
}
diff --git a/projects/components/block-ui/src/block-ui.component.spec.ts b/projects/components/block-ui/src/block-ui.component.spec.ts
index c91eb50..53672b9 100644
--- a/projects/components/block-ui/src/block-ui.component.spec.ts
+++ b/projects/components/block-ui/src/block-ui.component.spec.ts
@@ -19,7 +19,7 @@ import { ZvBlockUiHarness } from './testing/block-ui.harness';
})
export class TestComponent {
public blocked = false;
- public spinnerText: string = null;
+ public spinnerText = '';
}
describe('ZvBlockUi', () => {
From cef338017af057cf6f572001aeccfd55504c8073 Mon Sep 17 00:00:00 2001
From: Suneeh
Date: Thu, 3 Apr 2025 19:26:06 +0200
Subject: [PATCH 03/11] review comments
---
.../action-button/src/action-button.component.html | 4 ++--
.../action-button/src/action-button.component.spec.ts | 4 ++--
.../components/action-button/src/action-button.component.ts | 3 +--
.../components/action-button/src/action-button.harness.ts | 5 ++---
4 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/projects/components/action-button/src/action-button.component.html b/projects/components/action-button/src/action-button.component.html
index 0364c10..aaa02f8 100644
--- a/projects/components/action-button/src/action-button.component.html
+++ b/projects/components/action-button/src/action-button.component.html
@@ -1,14 +1,14 @@
@if (actionDs().succeeded()) {
check_circle
diff --git a/projects/components/action-button/src/action-button.component.spec.ts b/projects/components/action-button/src/action-button.component.spec.ts
index 5770fc2..96e4399 100644
--- a/projects/components/action-button/src/action-button.component.spec.ts
+++ b/projects/components/action-button/src/action-button.component.spec.ts
@@ -93,8 +93,8 @@ describe('ZvActionButton', () => {
});
it('should show label', async () => {
- const label = await harness.getButtonLabel();
- expect(await label?.text()).toBe('label');
+ const buttonContent = await harness.getButtonContent();
+ expect(buttonContent).toContain('label');
});
it('should show success icon', fakeAsync(async () => {
diff --git a/projects/components/action-button/src/action-button.component.ts b/projects/components/action-button/src/action-button.component.ts
index dbc46ea..0fedf54 100644
--- a/projects/components/action-button/src/action-button.component.ts
+++ b/projects/components/action-button/src/action-button.component.ts
@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, input, Signal } from '@angular/core
import { MatButtonModule } from '@angular/material/button';
import { ThemePalette } from '@angular/material/core';
import { MatIcon } from '@angular/material/icon';
-import { MatLabel } from '@angular/material/input';
import { ZvBlockUi } from '@zvoove/components/block-ui';
import { ZvErrorMessagePipe } from '@zvoove/components/core';
import { ZvActionDataSource } from './action-data-source';
@@ -20,7 +19,7 @@ export interface IZvActionButton {
templateUrl: './action-button.component.html',
styleUrl: './action-button.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [MatLabel, ZvErrorMessagePipe, ZvBlockUi, MatIcon, MatButtonModule],
+ imports: [ZvErrorMessagePipe, ZvBlockUi, MatIcon, MatButtonModule],
})
export class ZvActionButtonComponent {
public readonly actionDs = input.required>();
diff --git a/projects/components/action-button/src/action-button.harness.ts b/projects/components/action-button/src/action-button.harness.ts
index db46fb9..229976f 100644
--- a/projects/components/action-button/src/action-button.harness.ts
+++ b/projects/components/action-button/src/action-button.harness.ts
@@ -5,7 +5,6 @@ export class ZvActionButtonHarness extends ComponentHarness {
private _button = this.locatorForOptional('button');
private _buttonIcon = this.locatorForOptional('button mat-icon');
- private _buttonLabel = this.locatorForOptional('button mat-label');
private _blockOverlay = this.locatorForOptional('.zv-block-ui__overlay');
private _successDiv = this.locatorForOptional('.zv-action-button__check');
private _errorDiv = this.locatorForOptional('.zv-action-button__error');
@@ -18,8 +17,8 @@ export class ZvActionButtonHarness extends ComponentHarness {
return await this._buttonIcon();
}
- public async getButtonLabel(): Promise {
- return await this._buttonLabel();
+ public async getButtonContent(): Promise {
+ return (await (await this._button())?.text()) ?? null;
}
public async showsSuccess(): Promise {
From 6e7682008c83b8e7cf4bf53f5544d1eac5aae31a Mon Sep 17 00:00:00 2001
From: Suneeh
Date: Thu, 3 Apr 2025 20:17:52 +0200
Subject: [PATCH 04/11] extend harness
---
.../components/action-button/public_api.ts | 2 +-
.../src/action-button.component.spec.ts | 46 +++++++++----------
.../src/action-button.component.ts | 6 +--
.../src/action-button.harness.ts | 21 +++++++--
.../action-button/src/action-data-source.ts | 17 ++++---
5 files changed, 54 insertions(+), 38 deletions(-)
diff --git a/projects/components/action-button/public_api.ts b/projects/components/action-button/public_api.ts
index 72ce6c5..6c243da 100644
--- a/projects/components/action-button/public_api.ts
+++ b/projects/components/action-button/public_api.ts
@@ -1,2 +1,2 @@
export { ZvActionButtonComponent, type IZvActionButton } from './src/action-button.component';
-export { ZvActionDataSource, type ZvActionDataSourceOptions } from './src/action-data-source';
+export { ZvActionDataSource, type IZvActionDataSource, type ZvActionDataSourceOptions } from './src/action-data-source';
diff --git a/projects/components/action-button/src/action-button.component.spec.ts b/projects/components/action-button/src/action-button.component.spec.ts
index 96e4399..a93eb4a 100644
--- a/projects/components/action-button/src/action-button.component.spec.ts
+++ b/projects/components/action-button/src/action-button.component.spec.ts
@@ -2,7 +2,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { switchMap, throwError, timer } from 'rxjs';
+import { switchMap, tap, throwError, timer } from 'rxjs';
import { IZvActionButton, ZvActionButtonComponent } from './action-button.component';
import { ZvActionButtonHarness } from './action-button.harness';
import { ZvActionDataSource } from './action-data-source';
@@ -16,8 +16,9 @@ import { ZvActionDataSource } from './action-data-source';
})
export class TestComponent {
public readonly isDisabled = signal(false);
+ public actionFnCalled = false;
dataSource = new ZvActionDataSource({
- actionFn: () => timer(1),
+ actionFn: () => timer(1).pipe(tap(() => (this.actionFnCalled = true))),
});
actionButton: IZvActionButton = {
@@ -49,47 +50,46 @@ describe('ZvActionButton', () => {
expect(component).toBeDefined();
});
+ it('should call dataSource.execute() on click', async () => {
+ expect(component.actionFnCalled).toBeFalse();
+ await harness.click();
+ expect(component.actionFnCalled).toBeTrue();
+ });
+
it('should be blocked while pending', async () => {
- const button = await harness.getButton();
- button?.click();
+ component.dataSource.execute();
expect(component.dataSource.pending()).toBeTrue();
expect(await harness.isBlocked()).toBeTrue();
});
it('should be disabled while pending', async () => {
- const button = await harness.getButton();
- button?.click();
+ component.dataSource.execute();
expect(component.dataSource.pending()).toBeTrue();
- expect(await button!.getProperty('disabled')).toBeTrue();
+ expect(await harness.isDisabled()).toBeTrue();
});
it('should respect disabled property', async () => {
- const button = await harness.getButton();
- expect(await button?.getProperty('disabled')).toBeFalse();
+ expect(await harness?.isDisabled()).toBeFalse();
component.isDisabled.set(true);
- expect(await button!.getProperty('disabled')).toBeTrue();
+ expect(await harness?.isDisabled()).toBeTrue();
component.isDisabled.set(false);
- expect(await button?.getProperty('disabled')).toBeFalse();
+ expect(await harness?.isDisabled()).toBeFalse();
});
it('should respect color property', async () => {
- const button = await harness.getButton();
- expect(await button?.hasClass('mat-primary')).toBeTrue();
- component.actionButton.color = 'accent';
+ expect(await harness.hasClass('mat-primary')).toBeTrue();
component.actionButton = { ...component.actionButton, color: 'accent' };
- expect(await button?.hasClass('mat-accent')).toBeTrue();
+ expect(await harness.hasClass('mat-accent')).toBeTrue();
component.actionButton = { ...component.actionButton, color: 'warn' };
- expect(await button?.hasClass('mat-warn')).toBeTrue();
+ expect(await harness.hasClass('mat-warn')).toBeTrue();
});
it('should have dataCy attribute', async () => {
- const button = await harness.getButton();
- expect(await button?.getAttribute('data-cy')).toBe('dataCyTest');
+ expect(await harness.getDataCy()).toBe('dataCyTest');
});
it('should show icon', async () => {
- const icon = await harness.getButtonIcon();
- expect(await icon?.text()).toBe('home');
+ expect(await harness.getButtonIcon()).toBe('home');
});
it('should show label', async () => {
@@ -98,8 +98,7 @@ describe('ZvActionButton', () => {
});
it('should show success icon', fakeAsync(async () => {
- const button = await harness.getButton();
- button?.click();
+ await harness.click();
tick(1);
expect(component.dataSource.pending()).toBeFalse();
expect(component.dataSource.succeeded()).toBeTrue();
@@ -109,8 +108,7 @@ describe('ZvActionButton', () => {
it('should show error message', fakeAsync(async () => {
const error = new Error('action failed');
component.dataSource = new ZvActionDataSource({ actionFn: () => timer(1).pipe(switchMap(() => throwError(() => error))) });
- const button = await harness.getButton();
- button?.click();
+ await harness.click();
tick(1);
expect(await harness.showsSuccess()).toBeFalse();
expect(await (await harness.getError())?.text()).toBe(error.message);
diff --git a/projects/components/action-button/src/action-button.component.ts b/projects/components/action-button/src/action-button.component.ts
index 0fedf54..71efd82 100644
--- a/projects/components/action-button/src/action-button.component.ts
+++ b/projects/components/action-button/src/action-button.component.ts
@@ -4,7 +4,7 @@ import { ThemePalette } from '@angular/material/core';
import { MatIcon } from '@angular/material/icon';
import { ZvBlockUi } from '@zvoove/components/block-ui';
import { ZvErrorMessagePipe } from '@zvoove/components/core';
-import { ZvActionDataSource } from './action-data-source';
+import { IZvActionDataSource } from './action-data-source';
export interface IZvActionButton {
label: string;
@@ -21,7 +21,7 @@ export interface IZvActionButton {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ZvErrorMessagePipe, ZvBlockUi, MatIcon, MatButtonModule],
})
-export class ZvActionButtonComponent {
- public readonly actionDs = input.required>();
+export class ZvActionButtonComponent {
+ public readonly actionDs = input.required();
public readonly button = input.required();
}
diff --git a/projects/components/action-button/src/action-button.harness.ts b/projects/components/action-button/src/action-button.harness.ts
index 229976f..54a12e5 100644
--- a/projects/components/action-button/src/action-button.harness.ts
+++ b/projects/components/action-button/src/action-button.harness.ts
@@ -9,12 +9,13 @@ export class ZvActionButtonHarness extends ComponentHarness {
private _successDiv = this.locatorForOptional('.zv-action-button__check');
private _errorDiv = this.locatorForOptional('.zv-action-button__error');
- public async getButton(): Promise {
- return await this._button();
+ public async click(): Promise {
+ const button = await this._button();
+ return await button?.click();
}
- public async getButtonIcon(): Promise {
- return await this._buttonIcon();
+ public async getButtonIcon(): Promise {
+ return await (await this._buttonIcon())?.text();
}
public async getButtonContent(): Promise {
@@ -32,4 +33,16 @@ export class ZvActionButtonHarness extends ComponentHarness {
public async isBlocked(): Promise {
return !!(await this._blockOverlay());
}
+
+ public async isDisabled(): Promise {
+ return await (await this._button())?.getProperty('disabled');
+ }
+
+ public async hasClass(className: string): Promise {
+ return (await (await this._button())?.hasClass(className)) ?? false;
+ }
+
+ public async getDataCy(): Promise {
+ return (await (await this._button())?.getAttribute('data-cy')) ?? '';
+ }
}
diff --git a/projects/components/action-button/src/action-data-source.ts b/projects/components/action-button/src/action-data-source.ts
index 857862e..d963b9d 100644
--- a/projects/components/action-button/src/action-data-source.ts
+++ b/projects/components/action-button/src/action-data-source.ts
@@ -1,15 +1,22 @@
-import { signal } from '@angular/core';
+import { computed, Signal, signal } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
+export interface IZvActionDataSource {
+ readonly succeeded: Signal;
+ readonly pending: Signal;
+ readonly hasError: Signal;
+ readonly exception: Signal;
+ execute(): void;
+}
+
export interface ZvActionDataSourceOptions {
actionFn: () => Observable;
}
-export class ZvActionDataSource {
+export class ZvActionDataSource implements IZvActionDataSource {
private readonly _exception = signal(null);
private readonly _pending = signal(false);
- private readonly _hasError = signal(false);
private readonly _succeeded = signal(false);
private actionSub = Subscription.EMPTY;
@@ -18,13 +25,12 @@ export class ZvActionDataSource {
public readonly exception = this._exception.asReadonly();
public readonly pending = this._pending.asReadonly();
- public readonly hasError = this._hasError.asReadonly();
+ public readonly hasError = computed(() => this._exception() !== null);
public readonly succeeded = this._succeeded.asReadonly();
public execute() {
this.actionSub.unsubscribe();
this._pending.set(true);
- this._hasError.set(false);
this._exception.set(null);
this._succeeded.set(false);
@@ -38,7 +44,6 @@ export class ZvActionDataSource {
},
error: (err: unknown) => {
this._pending.set(false);
- this._hasError.set(true);
this._exception.set(err);
},
});
From 016cab7e54241c697b49019bd4c6f1b517f8ee2e Mon Sep 17 00:00:00 2001
From: Suneeh
Date: Fri, 4 Apr 2025 15:30:22 +0200
Subject: [PATCH 05/11] integrate icon and error into button
---
.../src/action-button.component.html | 38 ++++++++++---------
.../src/action-button.component.scss | 11 ++----
.../src/action-button.component.spec.ts | 6 +--
.../src/action-button.component.ts | 26 +++++++++++--
.../src/action-button.harness.ts | 18 +++------
.../src/action-data-source.spec.ts | 2 +-
.../action-button/src/action-data-source.ts | 10 +++--
7 files changed, 64 insertions(+), 47 deletions(-)
diff --git a/projects/components/action-button/src/action-button.component.html b/projects/components/action-button/src/action-button.component.html
index aaa02f8..560ee28 100644
--- a/projects/components/action-button/src/action-button.component.html
+++ b/projects/components/action-button/src/action-button.component.html
@@ -1,19 +1,21 @@
-
-
@@ -64,7 +75,7 @@ Try it:
Usage
- An example of how to use the IZvActionButton:
+ An example of how to use the zv-action-button:
TypeScript
HTML
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
index c4d0e06..3ae0c50 100644
--- a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { ThemePalette } from '@angular/material/core';
-import { IZvActionButton, ZvActionButtonComponent, ZvActionDataSource } from '@zvoove/components/action-button';
+import { ZvActionButtonComponent, ZvActionButtonDataSource } from '@zvoove/components/action-button';
import { of } from 'rxjs';
import { delay, map } from 'rxjs/operators';
import { allSharedImports } from '../common/shared-imports';
@@ -20,7 +20,7 @@ export class ActionButtonDemoComponent {
public readonly counter = signal(0);
public readonly isDisabled = signal(false);
- public dataSource = new ZvActionDataSource({
+ public dataSource = new ZvActionButtonDataSource({
actionFn: () => {
return of('foo').pipe(
delay(1000),
@@ -40,18 +40,8 @@ export class ActionButtonDemoComponent {
this.isDisabled.set(isChecked);
}
- getActionButton(color: ThemePalette | null): IZvActionButton {
- return {
- label: `I am an IZvActionButton (color: ${color})`,
- color: color,
- icon: 'home',
- dataCy: 'test',
- isDisabled: this.isDisabled,
- };
- }
-
importsCode = `
-import { IZvActionButton, ZvActionButtonComponent, ZvActionDataSource } from '@zvoove/components/action-button';
+import { ZvActionButtonComponent, ZvActionButtonDataSource } from '@zvoove/components/action-button';
// ...
imports: [
ZvActionButtonComponent,
@@ -59,20 +49,14 @@ imports: [
usageCodeTs = `
private http = inject(HttpClient);
-actionDataSource = new ZvActionDataSource({
+actionDataSource = new ZvActionButtonDataSource({
actionFn: () => this.http.post('https://YOUR.API/POST-ROUTE', {}),
});
-
-public actionButton: IZvActionButton = {
- label: '',
- color: null,
- icon: 'home',
- dataCy: 'testsWillFindMe',
- isDisabled: this.isDisabled,
-};
`;
usageCodeHtml = `
-
+
+ button label
+
`;
}
From 8865cb69eee2455bf6d8b9ad754a45164745f255 Mon Sep 17 00:00:00 2001
From: saithis <1547453+saithis@users.noreply.github.com>
Date: Sat, 5 Apr 2025 15:56:10 +0200
Subject: [PATCH 07/11] remove unused css
---
.../action-button/src/action-button.component.scss | 5 -----
.../components/action-button/src/action-button.component.ts | 1 -
2 files changed, 6 deletions(-)
delete mode 100644 projects/components/action-button/src/action-button.component.scss
diff --git a/projects/components/action-button/src/action-button.component.scss b/projects/components/action-button/src/action-button.component.scss
deleted file mode 100644
index 32dd2e8..0000000
--- a/projects/components/action-button/src/action-button.component.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.zv-action-button__button-content {
- display: flex;
- gap: 0.5em;
- align-items: center;
-}
diff --git a/projects/components/action-button/src/action-button.component.ts b/projects/components/action-button/src/action-button.component.ts
index c40dcf7..5066fb2 100644
--- a/projects/components/action-button/src/action-button.component.ts
+++ b/projects/components/action-button/src/action-button.component.ts
@@ -17,7 +17,6 @@ export interface IZvActionButton {
@Component({
selector: 'zv-action-button',
templateUrl: './action-button.component.html',
- styleUrl: './action-button.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatIcon, MatButtonModule, MatProgressSpinner, MatTooltip],
})
From d3d3b0e574b3df4497b27c9fd140cac31216d0c7 Mon Sep 17 00:00:00 2001
From: saithis <1547453+saithis@users.noreply.github.com>
Date: Sat, 5 Apr 2025 17:09:05 +0200
Subject: [PATCH 08/11] Fix review comments
---
.../action-button/src/action-button-data-source.ts | 7 +++----
.../action-button/src/action-button.component.html | 2 +-
.../action-button/src/action-button.component.ts | 6 +++---
.../action-button-demo/action-button-demo.component.html | 2 +-
.../app/action-button-demo/action-button-demo.component.ts | 5 ++---
projects/zvoove-components-demo/src/styles.scss | 1 +
6 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/projects/components/action-button/src/action-button-data-source.ts b/projects/components/action-button/src/action-button-data-source.ts
index a6fa23b..436766d 100644
--- a/projects/components/action-button/src/action-button-data-source.ts
+++ b/projects/components/action-button/src/action-button-data-source.ts
@@ -11,7 +11,7 @@ export interface IZvActionButtonDataSource {
execute(): void;
}
-export interface IZvActionButtonDataSourceOptions extends ZvActionDataSourceOptions {}
+export declare type IZvActionButtonDataSourceOptions = ZvActionDataSourceOptions;
export class ZvActionButtonDataSource extends ZvActionDataSource implements IZvActionButtonDataSource {
readonly #destroyRef = inject(DestroyRef);
@@ -27,11 +27,10 @@ export class ZvActionButtonDataSource extends ZvActionDataSource implements IZvA
constructor(options: IZvActionButtonDataSourceOptions) {
super(options);
- let timeoutRef: NodeJS.Timeout;
effect(() => {
if (this.showSuccess()) {
- clearTimeout(timeoutRef);
- timeoutRef = setTimeout(() => {
+ clearTimeout(this.#timeoutRef);
+ this.#timeoutRef = setTimeout(() => {
this.showSuccess.set(false);
}, 2000);
}
diff --git a/projects/components/action-button/src/action-button.component.html b/projects/components/action-button/src/action-button.component.html
index 1f1ae94..a5279fa 100644
--- a/projects/components/action-button/src/action-button.component.html
+++ b/projects/components/action-button/src/action-button.component.html
@@ -15,5 +15,5 @@
} @else if (icon()) {
{{ icon() }}
}
-
+
diff --git a/projects/components/action-button/src/action-button.component.ts b/projects/components/action-button/src/action-button.component.ts
index 5066fb2..1865e10 100644
--- a/projects/components/action-button/src/action-button.component.ts
+++ b/projects/components/action-button/src/action-button.component.ts
@@ -4,6 +4,7 @@ import { ThemePalette } from '@angular/material/core';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { MatTooltip } from '@angular/material/tooltip';
+import { ZvButtonColors } from '@zvoove/components/core';
import { IZvActionButtonDataSource } from './action-button-data-source';
export interface IZvActionButton {
@@ -23,15 +24,14 @@ export interface IZvActionButton {
export class ZvActionButtonComponent {
public readonly dataSource = input.required();
public readonly icon = input(null);
- public readonly color = input(null);
+ public readonly color = input(null);
public readonly disabled = input(false);
- private _tooltip = viewChild(MatTooltip);
+ private readonly _tooltip = viewChild(MatTooltip);
constructor() {
afterRenderEffect(() => {
if (this.dataSource().showError()) {
- console.log(this._tooltip());
this._tooltip()?.show(0);
}
});
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html
index d630253..aef0f02 100644
--- a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.html
@@ -19,7 +19,7 @@ Settings:
Try it:
@for (value of themePalletValues; track value) {
-
+
{{ value }} example
}
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
index 3ae0c50..1bd16e2 100644
--- a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
@@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
-import { ThemePalette } from '@angular/material/core';
import { ZvActionButtonComponent, ZvActionButtonDataSource } from '@zvoove/components/action-button';
+import { ZvButtonColors } from '@zvoove/components/core';
import { of } from 'rxjs';
import { delay, map } from 'rxjs/operators';
import { allSharedImports } from '../common/shared-imports';
@@ -16,7 +16,6 @@ import { allSharedImports } from '../common/shared-imports';
})
export class ActionButtonDemoComponent {
public loadError = false;
- public color: ThemePalette | null = null;
public readonly counter = signal(0);
public readonly isDisabled = signal(false);
@@ -35,7 +34,7 @@ export class ActionButtonDemoComponent {
},
});
- public themePalletValues: (ThemePalette | null)[] = [null, 'primary', 'accent', 'warn'];
+ public themePalletValues: (ZvButtonColors | null)[] = [null, 'primary', 'accent', 'warn'];
updateDisabled(isChecked: boolean) {
this.isDisabled.set(isChecked);
}
diff --git a/projects/zvoove-components-demo/src/styles.scss b/projects/zvoove-components-demo/src/styles.scss
index f85d081..15d419b 100644
--- a/projects/zvoove-components-demo/src/styles.scss
+++ b/projects/zvoove-components-demo/src/styles.scss
@@ -73,6 +73,7 @@ $theme: mat.define-theme(
// that you are using.
@include mat.all-component-themes($theme);
@include comp.zvoove-components-theme($theme);
+ @include mat.color-variants-backwards-compatibility($theme);
}
html,
From fad2a02a753cbcd64fd74a0c3072e0a8cacbcdf2 Mon Sep 17 00:00:00 2001
From: saithis <1547453+saithis@users.noreply.github.com>
Date: Sat, 5 Apr 2025 17:10:19 +0200
Subject: [PATCH 09/11] fix lint error
---
.../core/src/action-data-source/action-data-source.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/projects/components/core/src/action-data-source/action-data-source.ts b/projects/components/core/src/action-data-source/action-data-source.ts
index 2a4bd0f..dcac389 100644
--- a/projects/components/core/src/action-data-source/action-data-source.ts
+++ b/projects/components/core/src/action-data-source/action-data-source.ts
@@ -15,7 +15,7 @@ export interface ZvActionDataSourceOptions {
}
export class ZvActionDataSource implements IZvActionDataSource {
- readonly #state = signal<{ state: 'idle' | 'loading' | 'success' | 'error'; error: unknown | null }>({
+ readonly #state = signal<{ state: 'idle' | 'loading' | 'success' | 'error'; error: unknown }>({
state: 'idle',
error: null,
});
From f8ab8a2de8bdd0814c93cba3a458122375623f35 Mon Sep 17 00:00:00 2001
From: saithis <1547453+saithis@users.noreply.github.com>
Date: Sat, 5 Apr 2025 17:12:00 +0200
Subject: [PATCH 10/11] Change version to minor and update release notes
---
projects/components/package.json | 2 +-
.../src/app/upgrade-notes/upgrade-notes.page.html | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/projects/components/package.json b/projects/components/package.json
index 90eda66..1404087 100644
--- a/projects/components/package.json
+++ b/projects/components/package.json
@@ -1,7 +1,7 @@
{
"name": "@zvoove/components",
"description": "A set of angular components compatible with and/or dependent on @angular/material.",
- "version": "19.2.3",
+ "version": "19.3.0",
"license": "MIT",
"repository": {
"type": "git",
diff --git a/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html b/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html
index 3e73f95..363a305 100644
--- a/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html
+++ b/projects/zvoove-components-demo/src/app/upgrade-notes/upgrade-notes.page.html
@@ -1,5 +1,5 @@
-
- action: Add ZvActionButton
+
+ Added new zv-action-button component
fix: is24HourFormat not returning true for de-DE.
From 16671778108ebdd5be9f76832337f4b21ea4302f Mon Sep 17 00:00:00 2001
From: Suneeh
Date: Sat, 5 Apr 2025 17:42:55 +0200
Subject: [PATCH 11/11] fix usage code in demo
---
.../action-button/src/action-button.component.spec.ts | 3 ++-
.../app/action-button-demo/action-button-demo.component.ts | 6 +++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/projects/components/action-button/src/action-button.component.spec.ts b/projects/components/action-button/src/action-button.component.spec.ts
index 1f98dc1..661e699 100644
--- a/projects/components/action-button/src/action-button.component.spec.ts
+++ b/projects/components/action-button/src/action-button.component.spec.ts
@@ -6,6 +6,7 @@ import { tap, timer } from 'rxjs';
import { ZvActionButtonDataSource } from './action-button-data-source';
import { ZvActionButtonComponent } from './action-button.component';
import { ZvActionButtonHarness } from './action-button.harness';
+import { ZvButtonColors } from '@zvoove/components/core';
@Component({
selector: 'zv-test-component',
@@ -18,7 +19,7 @@ import { ZvActionButtonHarness } from './action-button.harness';
})
export class TestComponent {
public readonly isDisabled = signal(false);
- public readonly color = signal('primary');
+ public readonly color = signal('primary');
public actionFnCalled = false;
public throwError: Error | null = null;
dataSource = new ZvActionButtonDataSource({
diff --git a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
index 1bd16e2..88cf188 100644
--- a/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
+++ b/projects/zvoove-components-demo/src/app/action-button-demo/action-button-demo.component.ts
@@ -51,11 +51,11 @@ private http = inject(HttpClient);
actionDataSource = new ZvActionButtonDataSource({
actionFn: () => this.http.post('https://YOUR.API/POST-ROUTE', {}),
});
+public readonly isDisabled = signal(false);
+public readonly color() = signal('primary');
`;
usageCodeHtml = `
-
- button label
-
+label
`;
}