Enhance UI components with new input control directive and styling.

-  **Feature**: Added InputControlDirective for better input handling
- 🎨 **Style**: Updated button and text-field styles for loading states
- 🛠️ **Refactor**: Improved button component structure and disabled state handling
- 📚 **Docs**: Updated code style guidelines with new control flow syntax
This commit is contained in:
Lorenz Hilpert
2025-04-02 15:16:35 +02:00
parent a4b092a021
commit 67dcb49a1d
27 changed files with 302 additions and 89 deletions

View File

@@ -1,7 +1,5 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { ButtonColor, ButtonSize, ButtonComponent } from '@isa/ui/buttons';
// import { within } from '@storybook/testing-library';
// import { expect } from '@storybook/jest';
type UiButtonComponentInputs = {
color: ButtonColor;
@@ -15,23 +13,33 @@ const meta: Meta<UiButtonComponentInputs> = {
title: 'ui/buttons/Button',
argTypes: {
color: {
control: 'select',
control: { type: 'select' },
options: ['primary', 'secondary', 'brand', 'tertiary'] as ButtonColor[],
description: 'Determines the button color',
},
size: {
control: 'select',
control: { type: 'select' },
options: ['small', 'medium', 'large'] as ButtonSize[],
description: 'Determines the button size',
},
pending: {
control: 'boolean',
description: 'Show a pending/loading state when true',
},
disabled: {
control: 'boolean',
description: 'Disables the button when true',
},
},
args: {
color: 'primary',
size: 'medium',
pending: false,
disabled: false,
},
render: (args) => ({
props: args,
template: `<ui-button ${argsToTemplate(args, { exclude: ['disabled'] })} ${args.disabled ? 'disabled' : ''}>Button</ui-button>`,
template: `<button uiButton ${argsToTemplate(args)}>Button</button>`,
}),
};
export default meta;

View File

@@ -1,7 +1,5 @@
import { argsToTemplate, type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';
import { IconButtonColor, IconButtonSize, IconButtonComponent } from '@isa/ui/buttons';
// import { within } from '@storybook/testing-library';
// import { expect } from '@storybook/jest';
import { IsaIcons } from '@isa/icons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
@@ -23,30 +21,42 @@ const meta: Meta<UiIconButtonComponentInputs> = {
],
title: 'ui/buttons/IconButton',
argTypes: {
icon: { control: 'select', options: Object.keys(IsaIcons) },
icon: {
control: { type: 'select' },
options: Object.keys(IsaIcons),
description: 'Icon name for the button',
},
color: {
control: 'select',
control: { type: 'select' },
options: ['brand', 'primary', 'secondary', 'tertiary'] as IconButtonColor[],
description: 'Color style of the button',
},
size: {
control: 'select',
control: { type: 'select' },
options: ['small', 'medium'] as IconButtonSize[],
description: 'Size of the icon button',
},
pending: {
control: 'boolean',
description: 'Show a pending state when true',
},
disabled: {
control: 'boolean',
description: 'Disable the button when true',
},
},
args: {
icon: 'isaActionCheck',
color: 'primary',
size: 'medium',
pending: false,
disabled: false,
},
render: (args) => ({
props: args,
template: `<ui-icon-button ${argsToTemplate(args, { exclude: ['disabled', 'icon'] })} ${args.disabled ? 'disabled' : ''}>
template: `<button uiIconButton ${argsToTemplate(args, { exclude: ['icon'] })} >
<ng-icon name="${args.icon}"></ng-icon>
</ui-icon-button>`,
</button>`,
}),
};
export default meta;

View File

@@ -19,21 +19,37 @@ const meta: Meta<InfoButtonComponentInputs> = {
}),
],
title: 'ui/buttons/InfoButton',
args: {},
args: {
icon: 'isaNavigationLogout',
label: 'STA',
disabled: false,
pending: false,
},
argTypes: {
icon: { control: 'select', options: Object.keys(IsaIcons) },
label: { control: 'text' },
disabled: { control: 'boolean' },
icon: {
control: { type: 'select' },
options: Object.keys(IsaIcons),
description: 'Icon to display on the button',
},
label: {
control: 'text',
description: 'Label text inside the button',
},
disabled: {
control: 'boolean',
description: 'Disables the button when true',
},
pending: {
control: 'boolean',
description: 'Shows a pending state when true',
},
},
render: (args) => ({
props: args,
template: `<ui-info-button ${argsToTemplate(args, { exclude: ['disabled', 'icon'] })} ${args.disabled ? 'disabled' : ''}>
template: `<button uiInfoButton ${argsToTemplate(args, { exclude: ['icon'] })} >
<span uiInfoButtonLabel>${args.label}</span>
<ng-icon name="${args.icon}" uiInfoButtonIcon></ng-icon>
</ui-info-button>`,
</button>`,
}),
};

View File

@@ -1,35 +1,48 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { TextButtonComponent, TextButtonColor, TextButtonSize } from '@isa/ui/buttons';
// import { within } from '@storybook/testing-library';
// import { expect } from '@storybook/jest';
type UiTextButtonComponentInputs = {
export type UiTextButtonComponentInputs = {
color: TextButtonColor;
size: TextButtonSize;
disabled: boolean;
pending: boolean;
};
const meta: Meta<UiTextButtonComponentInputs> = {
component: TextButtonComponent,
title: 'ui/buttons/TextButton',
component: TextButtonComponent,
argTypes: {
color: {
control: 'select',
control: { type: 'select' },
options: ['strong', 'subtle'] as TextButtonColor[],
description: 'Color variation for the text button',
},
size: {
control: 'select',
control: { type: 'select' },
options: ['small', 'medium', 'large'] as TextButtonSize[],
description: 'Size of the text button',
},
disabled: {
control: 'boolean',
description: 'Disables the button when true',
},
pending: {
control: 'boolean',
description: 'Displays a loading state when true',
},
},
args: {
color: 'subtle',
size: 'medium',
disabled: false,
pending: false,
},
render: (args) => ({
props: args,
template: `<ui-text-button ${argsToTemplate(args, { exclude: ['disabled'] })} ${args.disabled ? 'disabled' : ''}>Button</ui-text-button>`,
template: `<button uiTextButton ${argsToTemplate(args)}>Button</button>`,
}),
};
export default meta;
type Story = StoryObj<TextButtonComponent>;
@@ -38,14 +51,6 @@ export const Default: Story = {
args: {},
};
// export const Heading: Story = {
// args: {},
// play: async ({ canvasElement }) => {
// const canvas = within(canvasElement);
// expect(canvas.getByText(/ui-button works!/gi)).toBeTruthy();
// },
// };
export const StrongSmall: Story = {
args: {
color: 'strong',

View File

@@ -175,6 +175,10 @@ function getUser(id) {
}
```
- **Templates**
- Use new control flow syntax - instead if \*ngIf use the @if syntax
## Project-Specific Preferences
- **Frameworks**: Follow best practices for Nx, Hono, and Zod.

View File

@@ -18,14 +18,4 @@ export default {
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
reporters: [
'default',
[
'jest-junit',
{
outputDirectory: 'testresults',
outputName: `TEST-oms-data-access.xml`,
},
],
],
};

View File

@@ -51,6 +51,8 @@
align-items: center;
gap: 0.5rem;
align-self: stretch;
@apply text-isa-secondary-900 isa-text-body-2-regular;
}
[lib-return-process-questions] {

View File

@@ -6,12 +6,20 @@
<div class="text-right">
<ui-text-field size="small" class="min-w-[22.875rem]">
<input
class="isa-text-body-2-bold"
uiInputControl
class="isa-text-body-2-bold placeholder:isa-text-body-2-bold"
placeholder="EAN eingeben / scannen"
type="text"
[formControl]="control"
/>
<button class="px-0" uiTextButton size="small" color="strong" (click)="check()">
<button
class="px-0"
uiTextButton
size="small"
color="strong"
(click)="check()"
[pending]="checking()"
>
Prüfen
</button>
</ui-text-field>
@@ -25,8 +33,8 @@
<div class="flex flex-row gap-4">
<img class="w-[3.375rem]" sharedProductImage [ean]="p.ean" [alt]="p.name" />
<div class="flex flex-col gap-2">
<div class="isa-text-body-2-bold">{{ p.contributors }}</div>
<div class="text-isa-secondary-900 isa-text-body-2-regular">{{ p.name }}</div>
<div class="isa-text-body-2-bold text-isa-secondary-900">{{ p.contributors }}</div>
<div class="text-isa-neutral-900 isa-text-body-2-regular">{{ p.name }}</div>
</div>
</div>
</div>

View File

@@ -9,7 +9,7 @@ import {
} from '@angular/forms';
import { Product, ReturnProcessProductQuestion, ReturnProcessStore } from '@isa/oms/data-access';
import { TextButtonComponent } from '@isa/ui/buttons';
import { TextFieldComponent } from '@isa/ui/input-controls';
import { InputControlDirective, TextFieldComponent } from '@isa/ui/input-controls';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { filter, pipe, switchMap, tap } from 'rxjs';
import { CatalougeSearchService } from '@isa/catalogue/data-access';
@@ -29,7 +29,13 @@ const eanValidator: ValidatorFn = (control: AbstractControl): ValidationErrors |
templateUrl: './return-process-product-question.component.html',
styleUrls: ['./return-process-product-question.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, TextFieldComponent, TextButtonComponent, ProductImageDirective],
imports: [
ReactiveFormsModule,
TextFieldComponent,
InputControlDirective,
TextButtonComponent,
ProductImageDirective,
],
})
export class ReturnProcessProductQuestionComponent {
#returnProcessStore = inject(ReturnProcessStore);

View File

@@ -7,10 +7,24 @@
font-style: normal;
font-weight: 700;
border-radius: 6.25rem;
position: relative;
}
.ui-button__pending {
@apply invisible;
}
.ui-button__spinner {
@apply animate-spin;
@apply visible animate-spin;
}
.ui-button__spinner-container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.ui-button__small {

View File

@@ -10,6 +10,24 @@
font-weight: 700;
cursor: pointer;
position: relative;
}
.ui-text-button__pending {
@apply invisible;
}
.ui-text-button__spinner {
@apply visible animate-spin;
}
.ui-text-button__spinner-container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.ui-text-button__normal {

View File

@@ -1,5 +1,7 @@
@if (!pending()) {
<ng-content></ng-content>
} @else {
<ng-icon class="ui-button__spinner" size="1.5rem" name="isaLoading"></ng-icon>
<ng-content></ng-content>
@if (pending()) {
<div class="ui-button__spinner-container">
<ng-icon class="ui-button__spinner" size="1.5rem" name="isaLoading"></ng-icon>
</div>
}

View File

@@ -1,9 +1,14 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { ButtonComponent } from './button.component';
import { MockComponent } from 'ng-mocks';
import { NgIconComponent } from '@ng-icons/core';
describe('ButtonComponent', () => {
let spectator: Spectator<ButtonComponent>;
const createComponent = createComponentFactory(ButtonComponent);
const createComponent = createComponentFactory({
component: ButtonComponent,
declarations: [MockComponent(NgIconComponent)],
});
beforeEach(() => {
spectator = createComponent();
@@ -42,4 +47,17 @@ describe('ButtonComponent', () => {
// Ensure previous color class is no longer present
expect(host.classList).not.toContain('ui-button__primary');
});
it('should update pending class when pending input changes', () => {
// Update the pending signal from its default value (false) to true
spectator.setInput('pending', true);
spectator.detectChanges();
const host = spectator.element;
expect(host.classList).toContain('ui-button__pending');
// Ensure the pending class is removed when set back to false
spectator.setInput('pending', false);
spectator.detectChanges();
expect(host.classList).not.toContain('ui-button__pending');
});
});

View File

@@ -10,21 +10,24 @@ import { ButtonColor, ButtonSize } from './types';
import { isaLoading } from '@isa/icons';
/**
* A UI button component that allows customization of size, color, and state.
* A UI button component that supports customization of size, color, and state.
*
* The component uses reactive signals to manage its properties, allowing dynamic
* updates to its CSS classes without manual DOM manipulation.
* The component uses reactive signals to dynamically update its CSS classes
* and properties without manual DOM manipulation.
*
* @property size - The size of the button (e.g., 'small', 'medium', 'large'). The default is 'medium'.
* @property size - The size of the button (e.g., 'small', 'medium', 'large'). Default is 'medium'.
* @property sizeClass - A computed CSS class based on the current size, formatted as 'ui-button__{size}'.
* @property color - The color theme of the button (e.g., 'primary', 'secondary'). The default is 'primary'.
* @property color - The color theme of the button (e.g., 'primary', 'secondary'). Default is 'primary'.
* @property colorClass - A computed CSS class based on the current color, formatted as 'ui-button__{color}'.
* @property pending - A boolean flag indicating whether the button is in a loading or pending state.
* @property pendingClass - A computed CSS class that adds a 'pending' style when the button is loading.
* @property tabIndex - The tab index for the button, used to control keyboard navigation order.
* @property disabled - A boolean flag indicating whether the button is disabled.
* @property disabledClass - A computed CSS class that adds a 'disabled' style when the button is disabled.
*
* @remarks
* This component relies on reactive input and computed signals to update its state and classes,
* ensuring that transformations are automatically reflected in the UI.
* This component is designed to be lightweight and flexible, leveraging Angular's
* reactive input and computed signals for seamless updates.
*/
@Component({
selector: 'ui-button, [uiButton]',
@@ -33,23 +36,38 @@ import { isaLoading } from '@isa/icons';
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [NgIconComponent],
// TODO: Gegen loader icon ersetzen
providers: [provideIcons({ isaLoading })],
host: {
'[class]': '["ui-button", sizeClass(), colorClass()]',
'[class]': '["ui-button", sizeClass(), colorClass(), pendingClass(), disabledClass()]',
'[tabindex]': 'tabIndex()',
'[disabled]': 'disabled()',
},
})
export class ButtonComponent {
/** The size of the button. */
size = input<ButtonSize>('medium');
/** A computed CSS class based on the current size. */
sizeClass = computed(() => `ui-button__${this.size()}`);
/** The color theme of the button. */
color = input<ButtonColor>('primary');
/** A computed CSS class based on the current color. */
colorClass = computed(() => `ui-button__${this.color()}`);
/** Indicates whether the button is in a loading or pending state. */
pending = input<boolean>(false);
/** A computed CSS class for the pending state. */
pendingClass = computed(() => (this.pending() ? 'ui-button__pending' : ''));
/** The tab index for the button. */
tabIndex = input<number>(0);
/** Indicates whether the button is disabled. */
disabled = input<boolean>(false);
/** A computed CSS class for the disabled state. */
disabledClass = computed(() => (this.disabled() ? 'disabled' : ''));
}

View File

@@ -1,5 +1,7 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { IconButtonComponent } from './icon-button.component';
import { MockComponent } from 'ng-mocks';
import { NgIconComponent } from '@ng-icons/core';
describe('IconButtonComponent', () => {
let spectator: Spectator<IconButtonComponent>;
@@ -7,7 +9,7 @@ describe('IconButtonComponent', () => {
const createComponent = createComponentFactory({
component: IconButtonComponent,
imports: [], // additional imports if needed
declarations: [MockComponent(NgIconComponent)],
});
beforeEach(() => {

View File

@@ -18,8 +18,9 @@ import { isaLoading } from '@isa/icons';
imports: [NgIconComponent],
providers: [provideIcons({ isaLoading })],
host: {
'[class]': "['ui-icon-button', sizeClass(), colorClass()]",
'[class]': "['ui-icon-button', sizeClass(), colorClass(), disabledClass()]",
'[tabindex]': 'tabIndex()',
'[disabled]': 'disabled()',
},
})
export class IconButtonComponent {
@@ -34,4 +35,10 @@ export class IconButtonComponent {
pending = input<boolean>(false);
tabIndex = input<number>(0);
/** Indicates whether the button is disabled. */
disabled = input<boolean>(false);
/** A computed CSS class for the disabled state. */
disabledClass = computed(() => (this.disabled() ? 'disabled' : ''));
}

View File

@@ -1,11 +1,13 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { InfoButtonComponent } from './info-button.component';
import { NgIconComponent } from '@ng-icons/core';
import { MockComponent } from 'ng-mocks';
describe('InfoButtonComponent', () => {
let spectator: Spectator<InfoButtonComponent>;
const createComponent = createComponentFactory({
component: InfoButtonComponent,
shallow: true,
declarations: [MockComponent(NgIconComponent)],
});
beforeEach(() => {

View File

@@ -16,11 +16,21 @@ import { NgIconComponent, provideIcons } from '@ng-icons/core';
imports: [NgIconComponent],
providers: [provideIcons({ isaLoading })],
host: {
'[class]': '["ui-info-button", pendingClass()]',
'[class]': '["ui-info-button", pendingClass(), disabledClass()]',
'[tabindex]': 'tabIndex()',
'[disabled]': 'disabled()',
},
})
export class InfoButtonComponent {
pending = input<boolean>(false);
pendingClass = computed(() => (this.pending() ? 'ui-info-button--pending' : ''));
tabIndex = input<number>(0);
/** Indicates whether the button is disabled. */
disabled = input<boolean>(false);
/** A computed CSS class for the disabled state. */
disabledClass = computed(() => (this.disabled() ? 'disabled' : ''));
}

View File

@@ -1 +1,7 @@
<ng-content></ng-content>
@if (pending()) {
<div class="ui-text-button__spinner-container">
<ng-icon class="ui-text-button__spinner" size="1.5rem" name="isaLoading"></ng-icon>
</div>
}

View File

@@ -1,10 +1,13 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { MockComponent } from 'ng-mocks';
import { TextButtonComponent } from './text-button.component';
import { NgIconComponent } from '@ng-icons/core';
describe('TextButtonComponent', () => {
let spectator: Spectator<TextButtonComponent>;
const createComponent = createComponentFactory({
component: TextButtonComponent,
declarations: [MockComponent(NgIconComponent)],
});
beforeEach(() => {
@@ -41,4 +44,18 @@ describe('TextButtonComponent', () => {
spectator.detectChanges();
expect(hostElement.getAttribute('tabindex')).toEqual('5');
});
it('should update pending class when pending input changes', () => {
// Set the pending input to true and verify the class is added
spectator.setInput('pending', true);
spectator.detectChanges();
const hostElement = spectator.element;
expect(hostElement.classList).toContain('ui-text-button__pending');
// Set the pending input back to false and verify the class is removed
spectator.setInput('pending', false);
spectator.detectChanges();
expect(hostElement.classList).not.toContain('ui-text-button__pending');
});
});

View File

@@ -6,27 +6,66 @@ import {
ViewEncapsulation,
} from '@angular/core';
import { TextButtonColor, TextButtonSize } from './types';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaLoading } from '@isa/icons';
/**
* A UI text button component that supports customization of size, color, and state.
*
* The component uses reactive signals to dynamically update its CSS classes
* and properties without manual DOM manipulation.
*
* @property size - The size of the button (e.g., 'small', 'medium', 'large'). Default is 'medium'.
* @property sizeClass - A computed CSS class based on the current size, formatted as 'ui-text-button__{size}'.
* @property color - The color theme of the button (e.g., 'normal', 'primary'). Default is 'normal'.
* @property colorClass - A computed CSS class based on the current color, formatted as 'ui-text-button__{color}'.
* @property pending - A boolean flag indicating whether the button is in a loading or pending state.
* @property pendingClass - A computed CSS class that adds a 'pending' style when the button is loading.
* @property tabIndex - The tab index for the button, used to control keyboard navigation order.
*
* @remarks
* This component is designed to be lightweight and flexible, leveraging Angular's
* reactive input and computed signals for seamless updates.
*/
@Component({
selector: 'ui-text-button, [uiTextButton]',
templateUrl: './text-button.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [],
imports: [NgIconComponent],
providers: [provideIcons({ isaLoading })],
host: {
'[class]': '["ui-text-button", sizeClass(), colorClass()]',
'[class]': '["ui-text-button", sizeClass(), colorClass(), pendingClass(), disabledClass()]',
'[tabindex]': 'tabIndex()',
'[disabled]': 'disabled()',
},
})
export class TextButtonComponent {
/** The size of the button. */
size = input<TextButtonSize>('medium');
/** A computed CSS class based on the current size. */
sizeClass = computed(() => `ui-text-button__${this.size()}`);
/** The color theme of the button. */
color = input<TextButtonColor>('normal');
/** A computed CSS class based on the current color. */
colorClass = computed(() => `ui-text-button__${this.color()}`);
/** Indicates whether the button is in a loading or pending state. */
pending = input<boolean>(false);
/** A computed CSS class for the pending state. */
pendingClass = computed(() => (this.pending() ? 'ui-text-button__pending' : ''));
/** The tab index for the button. */
tabIndex = input<number>(0);
/** Indicates whether the button is disabled. */
disabled = input<boolean>(false);
/** A computed CSS class for the disabled state. */
disabledClass = computed(() => (this.disabled() ? 'disabled' : ''));
}

View File

@@ -1,4 +1,5 @@
export * from './lib/checkbox/checkbox.component';
export * from './lib/core/input-control.directive';
export * from './lib/dropdown/dropdown.component';
export * from './lib/dropdown/dropdown.types';
export * from './lib/text-field/text-field.component';

View File

@@ -1,3 +1,4 @@
@use "./lib/checkbox/checkbox";
@use "./lib/chips/chips";
@use "./lib/dropdown/dropdown";
@use "./lib/text-field/text-field.scss";

View File

@@ -0,0 +1,17 @@
import { Directive, inject, input, OnInit } from '@angular/core';
import { NgControl } from '@angular/forms';
@Directive({ selector: 'input[uiInputControl]', host: { class: 'ui-input-control' } })
export class InputControlDirective<T> {
readonly control = inject(NgControl, { optional: true, self: true });
readonly value = input<T>();
getValue(): T | undefined {
if (this.control) {
return this.control.value as T;
}
return this.value();
}
}

View File

@@ -6,17 +6,14 @@
@apply border border-solid border-isa-neutral-600 rounded-full;
@apply bg-white text-isa-neutral-900;
input {
flex: 1;
appearance: none;
@apply isa-text-body-2-regular focus:outline-none;
@apply placeholder:text-neutral-500 hover:placeholder:text-neutral-900 focus:placeholder:text-neutral-900;
}
}
.ui-text-field__small {
.ui-input-control {
flex: 1;
appearance: none;
@apply isa-text-body-2-regular focus:outline-none;
@apply placeholder:text-neutral-500 hover:placeholder:text-neutral-900 focus:placeholder:text-neutral-900;
}
.ui-text-field__medium {

View File

@@ -1,10 +1,5 @@
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, contentChild, input } from '@angular/core';
import { InputControlDirective } from '../core/input-control.directive';
export const TextFieldSize = {
Small: 'small',
@@ -17,8 +12,6 @@ export type TextFieldSize = (typeof TextFieldSize)[keyof typeof TextFieldSize];
@Component({
selector: 'ui-text-field',
templateUrl: './text-field.component.html',
styleUrls: ['./text-field.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [],
@@ -27,6 +20,8 @@ export type TextFieldSize = (typeof TextFieldSize)[keyof typeof TextFieldSize];
},
})
export class TextFieldComponent {
inputControl = contentChild.required(InputControlDirective);
size = input<TextFieldSize>('medium');
sizeClass = computed(() => {

View File

@@ -4,7 +4,7 @@
"scripts": {
"ng": "ng",
"start": "nx serve isa-app --ssl",
"test": "nx test isa-app",
"test": "npx nx run-many -t test --exclude isa-app",
"ci": "npx nx run-many -t test --exclude isa-app -c ci",
"build": "nx build isa-app --configuration=development",
"build-prod": "nx build isa-app --configuration=production",