feat: refactor return feature components; remove deprecated files and implement new search bar input

This commit is contained in:
Lorenz Hilpert
2025-03-05 12:08:48 +01:00
parent 73be50e7d2
commit 86eb0bb494
45 changed files with 982 additions and 258 deletions

View File

@@ -43,7 +43,7 @@ const meta: Meta<UiSearchBarComponentInputs> = {
render: (args) => ({ render: (args) => ({
props: { ...args, control: new FormControl(args.value) }, props: { ...args, control: new FormControl(args.value) },
template: `<ui-search-bar ${argsToTemplate(args, { exclude: ['placeholder', 'value', 'resetValue'] })}> template: `<ui-search-bar ${argsToTemplate(args, { exclude: ['placeholder', 'value', 'resetValue'] })}>
<input [formControl]="control" type="text" uiSearchBarInput placeholder="${args.placeholder}" /> <input [formControl]="control" type="text" placeholder="${args.placeholder}" />
<ui-search-bar-clear value="${args.resetValue}"></ui-search-bar-clear> <ui-search-bar-clear value="${args.resetValue}"></ui-search-bar-clear>
<button type="submit" uiIconButton color="brand"> <button type="submit" uiIconButton color="brand">
<ng-icon name="isaActionSearch"></ng-icon> <ng-icon name="isaActionSearch"></ng-icon>

View File

@@ -1,10 +1,10 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { ProcessService } from './process.service'; import { ProcessService } from './process.service';
export function injectActivatedProcess() {
return inject(ProcessService).activatedProcess;
}
export function injectActivatedProcessId() { export function injectActivatedProcessId() {
return inject(ProcessService).activatedProcessId; return inject(ProcessService).activatedProcessId;
} }
export function injectActivatedProcess() {
return inject(ProcessService).activatedProcess;
}

View File

@@ -1,5 +0,0 @@
<h1 class="isa-typo-subtitle-1-regualr">Rückgabe starten</h1>
<p class="isa-typo-body-regular text-center">
Scannen Sie den QR-Code auf der Rechnung oder suchen Sie die Bestellung <br />
via Rechnungsnummer, E-Mail-Adresse oder Kundennamen
</p>

View File

@@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeatureReturnPagesComponent } from './feature-return-pages.component';
describe('FeatureReturnPagesComponent', () => {
let component: FeatureReturnPagesComponent;
let fixture: ComponentFixture<FeatureReturnPagesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FeatureReturnPagesComponent],
}).compileComponents();
fixture = TestBed.createComponent(FeatureReturnPagesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,10 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-feature-return-pages',
imports: [CommonModule],
templateUrl: './feature-return-pages.component.html',
styleUrl: './feature-return-pages.component.css',
})
export class FeatureReturnPagesComponent {}

View File

@@ -0,0 +1,11 @@
<h1 class="isa-text-subtitle-1-regular">Rückgabe starten</h1>
<p class="isa-text-body-1-regular text-center">
Scannen Sie den QR-Code auf der Rechnung oder suchen Sie die Bestellung <br />
via Rechnungsnummer, E-Mail-Adresse oder Kundennamen
</p>
<filter-search-bar-input
groupName="main"
inputKey="qs"
(search)="onSearch()"
></filter-search-bar-input>

View File

@@ -0,0 +1,7 @@
:host {
display: flex;
padding: 3rem 1.5rem 0rem 1.5rem;
flex-direction: column;
align-items: center;
gap: 2rem;
}

View File

@@ -0,0 +1,28 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ReturnSearchService } from '@feature/return/services';
import { injectActivatedProcessId } from '@isa/core/process';
import { FilterService, SearchBarInputComponent } from '@isa/shared/filter';
@Component({
selector: 'lib-main-page',
templateUrl: './main-page.component.html',
styleUrls: ['./main-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SearchBarInputComponent],
})
export class MainPageComponent {
processId = injectActivatedProcessId();
filterService = inject(FilterService);
returnSearchService = inject(ReturnSearchService);
onSearch() {
this.returnSearchService
.search({
filter: this.filterService.toParams(),
})
.subscribe();
}
}

View File

@@ -0,0 +1,7 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { ReturnSearchService } from '@feature/return/services';
import { QuerySettingsDTO } from '@generated/swagger/oms-api';
export const querySettingsResolverFn: ResolveFn<QuerySettingsDTO> = () =>
inject(ReturnSearchService).querySettings();

View File

@@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

@@ -0,0 +1,7 @@
:host {
display: flex;
padding: 3rem 1.5rem 0rem 1.5rem;
flex-direction: column;
align-items: center;
gap: 2rem;
}

View File

@@ -0,0 +1,12 @@
import { Component, inject } from '@angular/core';
import { ActivatedRoute, RouterOutlet } from '@angular/router';
import { provideQuerySettings } from '@isa/shared/filter';
@Component({
selector: 'lib-return-pages',
imports: [RouterOutlet],
templateUrl: './return-pages.component.html',
styleUrl: './return-pages.component.scss',
providers: [provideQuerySettings(() => inject(ActivatedRoute).snapshot.data['querySettings'])],
})
export class ReturnPagesComponent {}

View File

@@ -1,4 +1,13 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { FeatureReturnPagesComponent } from './feature-return-pages.component'; import { ReturnPagesComponent } from './return-pages.component';
import { querySettingsResolverFn } from './resolvers/query-settings.resolver-fn';
import { MainPageComponent } from './main/main-page.component';
export const routes: Routes = [{ path: '', component: FeatureReturnPagesComponent }]; export const routes: Routes = [
{
path: '',
component: ReturnPagesComponent,
resolve: { querySettings: querySettingsResolverFn },
children: [{ path: '', component: MainPageComponent }],
},
];

View File

@@ -1 +1 @@
export * from './lib/feature-return-services.component'; export * from './lib/return-search.service';

View File

@@ -1 +0,0 @@
<p>FeatureReturnServices works!</p>

View File

@@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeatureReturnServicesComponent } from './feature-return-services.component';
describe('FeatureReturnServicesComponent', () => {
let component: FeatureReturnServicesComponent;
let fixture: ComponentFixture<FeatureReturnServicesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FeatureReturnServicesComponent],
}).compileComponents();
fixture = TestBed.createComponent(FeatureReturnServicesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,10 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-feature-return-services',
imports: [CommonModule],
templateUrl: './feature-return-services.component.html',
styleUrl: './feature-return-services.component.css',
})
export class FeatureReturnServicesComponent {}

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
export const QueryTokenSchema = z.object({
filter: z.object({
qs: z.string().optional(),
}),
skip: z.number().default(0),
take: z.number().default(25),
});

View File

@@ -0,0 +1,33 @@
import { inject, Injectable } from '@angular/core';
import { QuerySettingsDTO, QueryTokenDTO, ReceiptService } from '@generated/swagger/oms-api';
import { map, Observable, throwError } from 'rxjs';
import { z } from 'zod';
import * as Schemas from './return-search.schemas';
@Injectable({ providedIn: 'root' })
export class ReturnSearchService {
#receiptService = inject(ReceiptService);
querySettings(): Observable<QuerySettingsDTO> {
return this.#receiptService.ReceiptQueryReceiptSettings().pipe(
map((res) => {
if (res.error || !res.result) {
throw new Error('Failed to fetch query settings');
}
return res.result;
}),
);
}
search(queryToken: z.input<typeof Schemas.QueryTokenSchema>): Observable<any> {
try {
queryToken = Schemas.QueryTokenSchema.parse(queryToken);
} catch (error) {
return throwError(() => error);
}
return this.#receiptService.ReceiptQueryReceipt({
queryToken,
});
}
}

View File

@@ -0,0 +1,22 @@
import { signalStore, withMethods } from '@ngrx/signals';
import {} from '@ngrx/operators';
import { withEntities } from '@ngrx/signals/entities';
import { pipe } from 'rxjs';
type ReturnSearuchState = {
id: number;
params: Record<string, string>;
results: string[];
hits: number;
status: 'idle' | 'pending' | 'success' | 'error';
error: string | null;
};
export const ReturnSearchStore = signalStore(
{ providedIn: 'root' },
withEntities<ReturnSearuchState>(),
// withMethods((store) => ({
// search: rxMethod<{ id: number; params: Record<string, string> }>(pipe()),
// paging: rxMethod<{ id: number }>(pipe()),
// })),
);

View File

@@ -0,0 +1,7 @@
# shared-filter
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test shared-filter` to execute the unit tests.

View File

@@ -0,0 +1,34 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../../eslint.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/angular'],
...nx.configs['flat/angular-template'],
{
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'filter',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'filter',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {},
},
];

View File

@@ -0,0 +1,21 @@
export default {
displayName: 'shared-filter',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/shared/filter',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@@ -0,0 +1,20 @@
{
"name": "shared-filter",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/shared/filter/src",
"prefix": "shared",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/shared/filter/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

@@ -0,0 +1,3 @@
export * from './lib/core';
export * from './lib/inputs';
export * from './lib/types';

View File

@@ -0,0 +1,57 @@
import { inject, Injectable, InjectionToken, Provider } from '@angular/core';
import { InputType, QuerySettingsDTO } from '../types';
import { patchState, signalState } from '@ngrx/signals';
import { mapToFilter } from './mappings';
export const QUERY_SETTINGS = new InjectionToken<QuerySettingsDTO>('QuerySettings');
export function provideQuerySettings(factory: () => QuerySettingsDTO): Provider[] {
return [{ provide: QUERY_SETTINGS, useFactory: factory }, FilterService];
}
@Injectable()
export class FilterService {
readonly settings = inject(QUERY_SETTINGS);
#state = signalState(mapToFilter(this.settings));
groups = this.#state.groups;
inputs = this.#state.inputs;
setInputValue(key: string, value: string | undefined): void {
const inputs = this.inputs().map((input) => {
if (input.key !== key) {
return input;
}
if (input.type === InputType.Text) {
return { ...input, value };
}
throw new Error(`Input type not supported: ${input.type}`);
});
patchState(this.#state, { inputs });
}
toParams(): Record<string, string> {
const params: Record<string, string> = {};
for (const input of this.inputs()) {
switch (input.type) {
case InputType.Text:
if (input.value) {
params[input.key] = input.value;
}
break;
}
}
return params;
}
parseParams(params: Record<string, string>): void {
throw new Error('Not implemented');
}
}

View File

@@ -0,0 +1,3 @@
export * from './filter.service';
export * from './mappings';
export * from './schemas';

View File

@@ -0,0 +1,102 @@
import { InputDTO, InputGroupDTO, InputType, OptionDTO, QuerySettingsDTO } from '../types';
import {
CheckboxFilterInput,
CheckboxFilterInputOption,
CheckboxFilterInputOptionSchema,
CheckboxFilterInputSchema,
DateRangeFilterInput,
DateRangeFilterInputSchema,
Filter,
FilterGroup,
FilterGroupSchema,
FilterInput,
TextFilterInput,
TextFilterInputSchema,
} from './schemas';
export function mapToFilter(settings: QuerySettingsDTO): Filter {
const filter: Filter = {
groups: [],
inputs: [],
};
const groups = [...settings.filter, ...settings.input];
for (const group of groups) {
filter.groups.push(mapToFilterGroup(group));
for (const input of group.input) {
filter.inputs.push(mapToFilterInput(group.group, input));
}
}
return filter;
}
function mapToFilterGroup(group: InputGroupDTO): FilterGroup {
return FilterGroupSchema.parse({
group: group.group,
label: group.label,
description: group.description,
});
}
function mapToFilterInput(group: string, input: InputDTO): FilterInput {
switch (input.type) {
case InputType.Text:
return mapToTextFilterInput(group, input);
case InputType.Checkbox:
return mapToCheckboxFilterInput(group, input);
case InputType.DateRange:
return mapToDateRangeFilterInput(group, input);
}
throw new Error(`Unknown input type: ${input.type}`);
}
function mapToTextFilterInput(group: string, input: InputDTO): TextFilterInput {
return TextFilterInputSchema.parse({
group,
key: input.key,
label: input.label,
description: input.description,
type: InputType.Text,
defaultValue: input.value,
value: input.value,
placeholder: input.placeholder,
});
}
function mapToCheckboxFilterInput(group: string, input: InputDTO): CheckboxFilterInput {
return CheckboxFilterInputSchema.parse({
group,
key: input.key,
label: input.label,
description: input.description,
type: InputType.Checkbox,
defaultValue: input.value,
maxOptions: input.options?.max,
options: input.options?.values?.map(mapToCheckboxOption),
selected:
input.options?.values?.filter((option) => option.selected).map((option) => option.value) ||
[],
});
}
function mapToCheckboxOption(option: OptionDTO): CheckboxFilterInputOption {
return CheckboxFilterInputOptionSchema.parse({
label: option.label,
value: option.value,
});
}
function mapToDateRangeFilterInput(group: string, input: InputDTO): DateRangeFilterInput {
return DateRangeFilterInputSchema.parse({
group,
key: input.key,
label: input.label,
description: input.description,
type: InputType.DateRange,
start: input.options?.values?.[0].value,
stop: input.options?.values?.[1].value,
});
}

View File

@@ -0,0 +1,74 @@
import { z } from 'zod';
import { InputType } from '../types';
export const FilterGroupSchema = z
.object({
group: z.string(),
label: z.string().optional(),
description: z.string().optional(),
})
.describe('FilterGroup');
export const CheckboxFilterInputOptionSchema = z
.object({
label: z.string(),
value: z.string(),
})
.describe('CheckboxFilterInputOption');
const BaseFilterInputSchema = z
.object({
group: z.string(),
key: z.string(),
label: z.string().optional(),
description: z.string().optional(),
type: z.nativeEnum(InputType),
})
.describe('BaseFilterInput');
export const TextFilterInputSchema = BaseFilterInputSchema.extend({
type: z.literal(InputType.Text),
placeholder: z.string().optional(),
defaultValue: z.string().optional(),
value: z.string().optional(),
}).describe('TextFilterInput');
export const CheckboxFilterInputSchema = BaseFilterInputSchema.extend({
type: z.literal(InputType.Checkbox),
maxOptions: z.number().optional(),
options: z.array(CheckboxFilterInputOptionSchema),
selected: z.array(z.string()),
}).describe('CheckboxFilterInput');
export const DateRangeFilterInputSchema = BaseFilterInputSchema.extend({
type: z.literal(InputType.DateRange),
start: z.string().optional(),
stop: z.string().optional(),
}).describe('DateRangeFilterInput');
export const FilterInputSchema = z.union([
TextFilterInputSchema,
CheckboxFilterInputSchema,
DateRangeFilterInputSchema,
]);
export const FilterSchema = z
.object({
groups: z.array(FilterGroupSchema),
inputs: z.array(FilterInputSchema),
})
.describe('Filter');
export type Filter = z.infer<typeof FilterSchema>;
export type FilterGroup = z.infer<typeof FilterGroupSchema>;
export type TextFilterInput = z.infer<typeof TextFilterInputSchema>;
export type CheckboxFilterInput = z.infer<typeof CheckboxFilterInputSchema>;
export type FilterInput = z.infer<typeof FilterInputSchema>;
export type CheckboxFilterInputOption = z.infer<typeof CheckboxFilterInputOptionSchema>;
export type DateRangeFilterInput = z.infer<typeof DateRangeFilterInputSchema>;

View File

@@ -0,0 +1 @@
export * from './search-bar-input';

View File

@@ -0,0 +1 @@
export * from './search-bar-input.component';

View File

@@ -0,0 +1,20 @@
@let inp = input();
@if (inp) {
<ui-search-bar>
<input
type="text"
[formControl]="control"
[placeholder]="inp.placeholder"
(keydown.enter)="onSearch()"
/>
<button
type="submit"
uiIconButton
color="brand"
[disabled]="control.invalid"
(click)="onSearch()"
>
<ng-icon name="isaActionSearch"></ng-icon>
</button>
</ui-search-bar>
}

View File

@@ -0,0 +1,47 @@
import { ChangeDetectionStrategy, Component, computed, inject, input, output } from '@angular/core';
import { UiSearchBarComponent } from '@isa/ui/search-bar';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { UiIconButtonComponent } from '@isa/ui/buttons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionSearch } from '@isa/icons';
import { FilterService, TextFilterInput } from '../../core';
import { InputType } from '../../types';
@Component({
selector: 'filter-search-bar-input',
templateUrl: './search-bar-input.component.html',
styleUrls: ['./search-bar-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [UiSearchBarComponent, UiIconButtonComponent, NgIconComponent, ReactiveFormsModule],
providers: [provideIcons({ isaActionSearch })],
})
export class SearchBarInputComponent {
readonly filterService = inject(FilterService);
control = new FormControl();
inputKey = input.required<string>();
input = computed<TextFilterInput>(() => {
const inputs = this.filterService.inputs();
const input = inputs.find(
(input) => input.key === this.inputKey() && input.type === InputType.Text,
) as TextFilterInput;
if (!input) {
throw new Error(`Input not found for key: ${this.inputKey()}`);
}
this.control.setValue(input.value);
return input;
});
search = output();
onSearch() {
this.filterService.setInputValue(this.inputKey(), this.control.value);
this.search.emit();
}
}

View File

@@ -0,0 +1,194 @@
export enum InputType {
Text = 1,
Checkbox = 2,
DateRange = 128,
}
export interface QuerySettingsDTO {
/**
* Filter
*/
filter: Array<InputGroupDTO>;
/**
* Eingabefelder
*/
input: Array<InputGroupDTO>;
/**
* Sortierung
*/
orderBy: Array<OrderByDTO>;
}
export interface InputGroupDTO {
/**
* Beschreibung
*/
description?: string;
/**
* Group / ID
*/
group: string;
/**
* Eingabefelder
*/
input: Array<InputDTO>;
/**
* Label
*/
label?: string;
}
/**
* Sortierwert
*/
export interface OrderByDTO {
/**
* Wert
*/
by?: string;
/**
* Absteigend
*/
desc?: boolean;
/**
* Label
*/
label?: string;
}
/**
* Eingabeelement
*/
export interface InputDTO {
/**
* Regex-Überprüfung
*/
constraint?: string;
/**
* Beschreibung
*/
description?: string;
/**
* Key / ID
*/
key: string;
/**
* Label
*/
label?: string;
/**
* Max-Wert (optional)
*/
maxValue?: string;
/**
* Min-Wert (optional)
*/
minValue?: string;
/**
* Auswahl
*/
options?: InputOptionsDTO;
/**
* Wasserzeichen
*/
placeholder?: string;
/**
* Anwendungsziel
*/
target?: string;
/**
* Art des Werts
*/
type: InputType;
/**
* Wert
*/
value?: string;
}
/**
* Auswahl
*/
export interface InputOptionsDTO {
/**
* Maximale Anzahl auswählbarer Elemente (null => alle, 1 = single select)
*/
max?: number;
/**
* Werte
*/
values?: Array<OptionDTO>;
}
/**
* Auswahlelement
*/
export interface OptionDTO {
/**
* Beschreibung
*/
description?: string;
/**
* Aktiv
*/
enabled?: boolean;
/**
* Key / ID
*/
key?: string;
/**
* Label
*/
label?: string;
/**
* Max-Wert (optional)
*/
maxValue?: string;
/**
* Min-Wert (optional)
*/
minValue?: string;
/**
* Wasserzeichen
*/
placeholder?: string;
/**
* Ausgewählt / Default
*/
selected?: boolean;
/**
* Wert
*/
value?: string;
/**
* Unter-Optionen
*/
values?: Array<OptionDTO>;
}

View File

@@ -0,0 +1,6 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv({
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
});

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es2022",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"target": "es2016",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -1,6 +1,4 @@
<ng-content select="[uiSearchBarInput]"></ng-content> <ng-content select="input[type=text]"></ng-content>
@let control = inputControl();
<div class="ui-search-bar__actions"> <div class="ui-search-bar__actions">
<ng-content select="ui-search-bar-clear"></ng-content> <ng-content select="ui-search-bar-clear"></ng-content>

View File

@@ -5,18 +5,14 @@ import {
ElementRef, ElementRef,
ViewEncapsulation, ViewEncapsulation,
} from '@angular/core'; } from '@angular/core';
import { isaActionClose } from '@isa/icons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { NgControl } from '@angular/forms'; import { NgControl } from '@angular/forms';
@Component({ @Component({
selector: 'ui-search-bar', selector: 'ui-search-bar',
imports: [NgIconComponent],
templateUrl: './search-bar.component.html', templateUrl: './search-bar.component.html',
styleUrl: './search-bar.component.scss', styleUrl: './search-bar.component.scss',
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideIcons({ isaActionClose })],
host: { host: {
'[class]': '["ui-search-bar"]', '[class]': '["ui-search-bar"]',
}, },

View File

@@ -1,209 +1,209 @@
const plugin = require("tailwindcss/plugin"); const plugin = require('tailwindcss/plugin');
module.exports = plugin(function ({ addBase, addUtilities }) { module.exports = plugin(function ({ addBase, addUtilities }) {
addBase({ addBase({
".text-h1": { fontSize: "3.375rem", lineHeight: "4.5rem" }, '.text-h1': { fontSize: '3.375rem', lineHeight: '4.5rem' },
".text-h2": { fontSize: "1.625rem", lineHeight: "2.25rem" }, '.text-h2': { fontSize: '1.625rem', lineHeight: '2.25rem' },
".text-h3": { fontSize: "1.375rem", lineHeight: "1.75rem" }, '.text-h3': { fontSize: '1.375rem', lineHeight: '1.75rem' },
".text-p1": { fontSize: "1.125rem", lineHeight: "1.5rem" }, '.text-p1': { fontSize: '1.125rem', lineHeight: '1.5rem' },
".text-p2": { fontSize: "1rem", lineHeight: "1.313rem" }, '.text-p2': { fontSize: '1rem', lineHeight: '1.313rem' },
".text-p3": { fontSize: "0.875rem", lineHeight: "1.188rem" }, '.text-p3': { fontSize: '0.875rem', lineHeight: '1.188rem' },
".text-p4": { fontSize: "0.75rem", lineHeight: "1rem" }, '.text-p4': { fontSize: '0.75rem', lineHeight: '1rem' },
}); });
addUtilities({ addUtilities({
".isa-text-heading-1-bold": { '.isa-text-heading-1-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "3.75rem", fontSize: '3.75rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "4.5rem", lineHeight: '4.5rem',
letterSpacing: "0.02813rem", letterSpacing: '0.02813rem',
}, },
".isa-text-heading-2-bold": { '.isa-text-heading-2-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "3rem", fontSize: '3rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "4rem", lineHeight: '4rem',
}, },
".isa-text-heading-3-bold": { '.isa-text-heading-3-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "2.5rem", fontSize: '2.5rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "3rem", lineHeight: '3rem',
}, },
".isa-text-subtitle-1-regular": { '.isa-text-subtitle-1-regular': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.75rem", fontSize: '1.75rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "2.5rem", lineHeight: '2.5rem',
}, },
".isa-text-subtitle-1-bold": { '.isa-text-subtitle-1-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.75rem", fontSize: '1.75rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "2.5rem", lineHeight: '2.5rem',
}, },
".isa-text-subtitle-2-bold": { '.isa-text-subtitle-2-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1rem", fontSize: '1rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.5rem", lineHeight: '1.5rem',
letterSpacing: "0.025rem", letterSpacing: '0.025rem',
textTransform: "uppercase", textTransform: 'uppercase',
}, },
".isa-text-body-1-bold": { '.isa-text-body-1-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1rem", fontSize: '1rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.5rem", lineHeight: '1.5rem',
}, },
".isa-text-body-1-bold-big": { '.isa-text-body-1-bold-big': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.25rem", fontSize: '1.25rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.75rem", lineHeight: '1.75rem',
}, },
".isa-text-body-1-bold-xl": { '.isa-text-body-1-bold-xl': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.375rem", fontSize: '1.375rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "2.125rem", lineHeight: '2.125rem',
}, },
".isa-text-body-1-semibold": { '.isa-text-body-1-semibold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1rem", fontSize: '1rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "600", fontWeight: '600',
lineHeight: "1.5rem", lineHeight: '1.5rem',
}, },
".isa-text-body-1-regular": { '.isa-text-body-1-regular': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1rem", fontSize: '1rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.5rem", lineHeight: '1.5rem',
}, },
".isa-text-body-1-regular-big": { '.isa-text-body-1-regular-big': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.25rem", fontSize: '1.25rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.75rem", lineHeight: '1.75rem',
}, },
".isa-text-body-1-regular-xl": { '.isa-text-body-1-regular-xl': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.375rem", fontSize: '1.375rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "2.125rem", lineHeight: '2.125rem',
}, },
".isa-text-body-2-bold": { '.isa-text-body-2-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
".isa-text-body-2-bold-big": { '.isa-text-body-2-bold-big': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.125rem", fontSize: '1.125rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.625rem", lineHeight: '1.625rem',
}, },
".isa-text-body-2-bold-xl": { '.isa-text-body-2-bold-xl': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.25rem", fontSize: '1.25rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.75rem", lineHeight: '1.75rem',
}, },
".isa-text-body-2-semibold": { '.isa-text-body-2-semibold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "600", fontWeight: '600',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
".isa-text-body-2-regular": { '.isa-text-body-2-regular': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
".isa-text-body-2-regular-big": { '.isa-text-body-2-regular-big': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.125rem", fontSize: '1.125rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.625rem", lineHeight: '1.625rem',
}, },
".isa-text-body-2-regular-xl": { '.isa-text-body-2-regular-xl': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "1.125rem", fontSize: '1.125rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.75rem", lineHeight: '1.75rem',
}, },
".isa-text-caption-bold": { '.isa-text-caption-bold': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.75rem", fontSize: '0.75rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1rem", lineHeight: '1rem',
}, },
".isa-text-caption-bold-big": { '.isa-text-caption-bold-big': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
".isa-text-caption-bold-xl": { '.isa-text-caption-bold-xl': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
".isa-text-caption-caps": { '.isa-text-caption-caps': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.75rem", fontSize: '0.75rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "700", fontWeight: '700',
lineHeight: "1rem", lineHeight: '1rem',
textTransform: "uppercase", textTransform: 'uppercase',
}, },
".isa-text-caption-regular": { '.isa-text-caption-regular': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.75rem", fontSize: '0.75rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1rem", lineHeight: '1rem',
}, },
".isa-text-caption-regular-big": { '.isa-text-caption-regular-big': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
".isa-text-caption-regular-xl": { '.isa-text-caption-regular-xl': {
fontFamily: "Open Sans", fontFamily: 'Open Sans',
fontSize: "0.875rem", fontSize: '0.875rem',
fontStyle: "normal", fontStyle: 'normal',
fontWeight: "400", fontWeight: '400',
lineHeight: "1.25rem", lineHeight: '1.25rem',
}, },
}); });
}); });

View File

@@ -2,7 +2,7 @@ const plugin = require('tailwindcss/plugin');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: ['./apps/**/*.{html,ts}'], content: ['./apps/**/*.{html,ts}', './libs/**/*.{html,ts}'],
darkMode: 'media', // or 'media' or 'class', darkMode: 'media', // or 'media' or 'class',
theme: { theme: {
screens: { screens: {

View File

@@ -46,6 +46,7 @@
"@hub/*": ["apps/isa-app/src/hub/*/index.ts"], "@hub/*": ["apps/isa-app/src/hub/*/index.ts"],
"@isa/core/process": ["libs/core/process/src/index.ts"], "@isa/core/process": ["libs/core/process/src/index.ts"],
"@isa/icons": ["libs/icons/src/index.ts"], "@isa/icons": ["libs/icons/src/index.ts"],
"@isa/shared/filter": ["libs/shared/filter/src/index.ts"],
"@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"], "@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"],
"@isa/ui/input-controls": ["libs/ui/input-controls/src/index.ts"], "@isa/ui/input-controls": ["libs/ui/input-controls/src/index.ts"],
"@isa/ui/search-bar": ["libs/ui/search-bar/src/index.ts"], "@isa/ui/search-bar": ["libs/ui/search-bar/src/index.ts"],