mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
UI Input,select,form-control Libs
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<form *ngIf="control" [formGroup]="control">
|
||||
<ng-container formGroupName="organisation">
|
||||
<ui-form-control label="Firmenname" requiredMark="*">
|
||||
<input uiInput type="text" formControlName="name" tabindex="1" />
|
||||
</ui-form-control>
|
||||
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Abteilung" [clearable]="false">
|
||||
<input uiInput type="text" formControlName="department" tabindex="2" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="USt-ID" [clearable]="false">
|
||||
<input uiInput type="text" formControlName="vatId" tabindex="3" />
|
||||
</ui-form-control>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Anrede" [clearable]="false">
|
||||
<ui-select formControlName="gender" tabindex="4">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Titel">
|
||||
<ui-select formControlName="title" tabindex="5">
|
||||
<ui-select-option value="Dr." label="Dr."></ui-select-option>
|
||||
<ui-select-option value="Prof." label="Prof."></ui-select-option>
|
||||
<ui-select-option value="Prof. Dr." label="Prof. Dr."></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Nachname" [clearable]="false">
|
||||
<input uiInput type="text" formControlName="lastName" tabindex="6" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Vorname" [clearable]="false">
|
||||
<input uiInput type="text" formControlName="firstName" tabindex="7" />
|
||||
</ui-form-control>
|
||||
</div>
|
||||
|
||||
<ng-container formGroupName="address">
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Straße" requiredMark="*">
|
||||
<input uiInput type="text" formControlName="street" tabindex="8" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Hausnummer" requiredMark="*">
|
||||
<input uiInput type="text" formControlName="streetNumber" tabindex="9" />
|
||||
</ui-form-control>
|
||||
</div>
|
||||
|
||||
<div class="control-row">
|
||||
<ui-form-control label="PLZ" requiredMark="*">
|
||||
<input uiInput type="text" formControlName="zipCode" tabindex="10" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Ort" requiredMark="*">
|
||||
<input uiInput type="text" formControlName="city" tabindex="11" />
|
||||
</ui-form-control>
|
||||
</div>
|
||||
|
||||
<ui-form-control label="Adresszusatz" [clearable]="false">
|
||||
<input uiInput type="text" formControlName="info" tabindex="12" />
|
||||
</ui-form-control>
|
||||
|
||||
<ui-form-control label="Land" [clearable]="false">
|
||||
<ui-select formControlName="country" tabindex="13">
|
||||
<ui-select-option value="DEU" label="Deutschland"></ui-select-option>
|
||||
</ui-select>
|
||||
</ui-form-control>
|
||||
</ng-container>
|
||||
|
||||
<ng-container formGroupName="communicationDetails">
|
||||
<ui-form-control label="E-Mail" [clearable]="false">
|
||||
<input uiInput type="mail" formControlName="email" tabindex="14" />
|
||||
</ui-form-control>
|
||||
|
||||
<div class="control-row">
|
||||
<ui-form-control label="Festnetznummer">
|
||||
<input uiInput type="tel" formControlName="phone" tabindex="15" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Mobilnummer" [clearable]="false">
|
||||
<input uiInput type="tel" formControlName="mobile" tabindex="16" />
|
||||
</ui-form-control>
|
||||
</div>
|
||||
</ng-container>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.control-row {
|
||||
@apply flex flex-row gap-8;
|
||||
|
||||
ui-form-control {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { CustomerDTO } from '@swagger/crm';
|
||||
|
||||
@Component({
|
||||
selector: 'page-create-b2b',
|
||||
templateUrl: 'create-b2b-form.component.html',
|
||||
styleUrls: ['create-b2b-form.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CreateB2BComponent {
|
||||
@Input()
|
||||
customer: CustomerDTO;
|
||||
|
||||
control: FormGroup;
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
initForm() {
|
||||
const { fb } = this;
|
||||
|
||||
this.control = fb.group({
|
||||
organisation: fb.group({
|
||||
name: fb.control('', [Validators.required]),
|
||||
department: fb.control(''),
|
||||
vatId: fb.control(''),
|
||||
}),
|
||||
gender: fb.control(0),
|
||||
title: fb.control(''),
|
||||
firstName: fb.control(''),
|
||||
lastName: fb.control(''),
|
||||
address: fb.group({
|
||||
street: fb.control('', [Validators.required]),
|
||||
streetNumber: fb.control('', [Validators.required]),
|
||||
zipCode: fb.control('', [Validators.required]),
|
||||
city: fb.control('', [Validators.required]),
|
||||
info: fb.control(''),
|
||||
country: fb.control('DEU', [Validators.required]),
|
||||
}),
|
||||
communicationDetails: fb.group({
|
||||
email: fb.control(''),
|
||||
phone: fb.control(''),
|
||||
mobile: fb.control(''),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="card">
|
||||
<h1>Kundendaten erfassen</h1>
|
||||
<p>
|
||||
Für eine B2B Bestellung benötigen Sie <br />
|
||||
einen Firmenaccount. Wir legen diesen <br />
|
||||
gerne direkt für Sie an.
|
||||
</p>
|
||||
|
||||
<page-customer-type-selector [(ngModel)]="type" (ngModelChange)="setType($event)"></page-customer-type-selector>
|
||||
|
||||
<div class="router-outlet-wrapper">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
:host {
|
||||
@apply flex flex-col box-border;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white rounded-card p-card;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply m-0;
|
||||
font-size: 26px;
|
||||
margin-top: 27px;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply m-0;
|
||||
font-size: 22px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.card > h1,
|
||||
.card > p {
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
page-customer-type-selector {
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
.router-outlet-wrapper {
|
||||
max-width: 650px;
|
||||
@apply mx-auto;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-create',
|
||||
templateUrl: 'customer-create.component.html',
|
||||
styleUrls: ['customer-create.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomerCreateComponent implements OnInit {
|
||||
type: string;
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.type = this.activatedRoute.snapshot.queryParams.type || 'guest';
|
||||
}
|
||||
|
||||
setType(type: string) {
|
||||
this.router.navigate(['./', type], {
|
||||
relativeTo: this.activatedRoute,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CustomerCreateComponent } from './customer-create.component';
|
||||
import { CustomerTypeSelectorComponent } from './customer-type-selector/customer-type-selector.component';
|
||||
import { UiFormControlModule } from '@ui/form-control';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
import { CreateB2BComponent } from './create-b2b-form/create-b2b-form.component';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiSelectModule } from '@ui/select';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiFormControlModule, UiInputModule, FormsModule, ReactiveFormsModule, RouterModule, UiSelectModule],
|
||||
exports: [CustomerCreateComponent, CreateB2BComponent],
|
||||
declarations: [CustomerCreateComponent, CustomerTypeSelectorComponent, CreateB2BComponent],
|
||||
})
|
||||
export class CustomerCreateModule {}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<ui-form-control label="Gastkunde">
|
||||
<input type="radio" name="customerType" value="guest" uiInput [ngModel]="value" (ngModelChange)="setValue($event)" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="Onlinekonto">
|
||||
<input type="radio" name="customerType" value="online" uiInput [ngModel]="value" (ngModelChange)="setValue($event)" />
|
||||
</ui-form-control>
|
||||
<ui-form-control label="B2B Kunde">
|
||||
<input type="radio" name="customerType" value="b2b" uiInput [ngModel]="value" (ngModelChange)="setValue($event)" />
|
||||
</ui-form-control>
|
||||
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
@apply flex flex-row justify-center gap-6;
|
||||
}
|
||||
|
||||
ui-form-control {
|
||||
@apply text-card-sub;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply text-card-sub;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, forwardRef } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'page-customer-type-selector',
|
||||
templateUrl: 'customer-type-selector.component.html',
|
||||
styleUrls: ['customer-type-selector.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => CustomerTypeSelectorComponent),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CustomerTypeSelectorComponent implements ControlValueAccessor {
|
||||
@Input()
|
||||
value: 'guest' | 'online' | 'b2b' = 'guest';
|
||||
|
||||
@Output()
|
||||
valueChange = new EventEmitter<'guest' | 'online' | 'b2b'>();
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
private onChange = (value: 'guest' | 'online' | 'b2b') => {};
|
||||
private onTouched = () => {};
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.value = obj;
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
setValue(value: 'guest' | 'online' | 'b2b') {
|
||||
this.onChange(value);
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="control-field">
|
||||
<ui-form-control label="Anrede" variant="inline">
|
||||
<ui-select #genderControl [(value)]="customer.gender" [disabled]="!genderToggle.toggled">
|
||||
<ui-select #genderControl [(ngModel)]="customer.gender" [disabled]="!genderToggle.toggled">
|
||||
<ui-select-option [value]="2" label="Herr"></ui-select-option>
|
||||
<ui-select-option [value]="4" label="Frau"></ui-select-option>
|
||||
</ui-select>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<a class="card-create-customer">
|
||||
<a class="card-create-customer" [routerLink]="['/customer', 'create']">
|
||||
<span class="title"> Kundendaten erfassen </span>
|
||||
</a>
|
||||
<div
|
||||
class="card-search-customer"
|
||||
*ngIf="search.searchState$ | async as searchState"
|
||||
>
|
||||
<div class="card-search-customer" *ngIf="search.searchState$ | async as searchState">
|
||||
<h1 class="title">Kundensuche</h1>
|
||||
<p class="info">
|
||||
Wie lautet Ihr Name oder <br />
|
||||
|
||||
@@ -24,3 +24,7 @@
|
||||
.card-search-customer {
|
||||
height: calc(100vh - 380px);
|
||||
}
|
||||
|
||||
a {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CreateB2BComponent } from './customer-create/create-b2b-form/create-b2b-form.component';
|
||||
import { CustomerCreateComponent } from './customer-create/customer-create.component';
|
||||
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
|
||||
import { CustomerSearchComponent } from './customer-search/customer-search.component';
|
||||
import { CustomerSearchMainComponent } from './customer-search/search-main/search-main.component';
|
||||
@@ -20,6 +22,15 @@ const routes: Routes = [
|
||||
{ path: ':customerId/details', component: null },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: CustomerCreateComponent,
|
||||
children: [
|
||||
{ path: 'guest', component: CreateB2BComponent },
|
||||
{ path: 'online', component: CreateB2BComponent },
|
||||
{ path: 'b2b', component: CreateB2BComponent },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ':customerId',
|
||||
component: CustomerDetailsComponent,
|
||||
|
||||
@@ -6,9 +6,19 @@ import { PageCustomerRoutingModule } from './page-customer-routing.module';
|
||||
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
|
||||
import { CustomerSearchModule } from './customer-search/customer-search.module';
|
||||
import { CustomerDetailsModule } from './customer-details/customer-details.module';
|
||||
import { CustomerCreateModule } from './customer-create/customer-create.module';
|
||||
import { UiInputModule } from '@ui/input';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, PageCustomerRoutingModule, ShellBreadcrumbModule, CustomerSearchModule, CustomerDetailsModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
PageCustomerRoutingModule,
|
||||
ShellBreadcrumbModule,
|
||||
CustomerSearchModule,
|
||||
CustomerDetailsModule,
|
||||
CustomerCreateModule,
|
||||
UiInputModule,
|
||||
],
|
||||
exports: [PageCustomerComponent],
|
||||
declarations: [PageCustomerComponent],
|
||||
})
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
<label *ngIf="label">{{ label }}</label>
|
||||
<ng-content select="input, ui-select"></ng-content>
|
||||
<button *ngIf="clearable && control.value && !control.disabled" class="ui-form-control-clear clear" (click)="control.clear()">
|
||||
<div class="input-wrapper" [class.empty]="!ngControl.value" [class.focused]="uiControl.focused | async">
|
||||
<ng-content select="input[uiInput], ui-select"></ng-content>
|
||||
<label *ngIf="label" (click)="uiControl.focus()">{{ label }}{{ requiredMark }}</label>
|
||||
</div>
|
||||
<span class="hint" *ngIf="ngControl.touched && ngControl.errors">
|
||||
{{ ngControl.errors | uiFormControlFirstError: label }}
|
||||
</span>
|
||||
<button
|
||||
*ngIf="clearable && ngControl?.value && !ngControl?.disabled && !(uiControl.type === 'radio' || uiControl.type === 'checkbox')"
|
||||
class="ui-form-control-clear clear"
|
||||
(click)="uiControl.clear()"
|
||||
>
|
||||
<ui-icon icon="close"></ui-icon>
|
||||
</button>
|
||||
|
||||
@@ -1,20 +1,116 @@
|
||||
:host {
|
||||
@apply flex flex-row gap-4 bg-white items-center box-border;
|
||||
}
|
||||
|
||||
:host ::ng-deep ui-select,
|
||||
:host ::ng-deep input {
|
||||
@apply font-bold flex-grow;
|
||||
}
|
||||
|
||||
:host ::ng-deep input {
|
||||
@apply outline-none border-none font-bold text-regular;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-white text-black;
|
||||
}
|
||||
@apply flex flex-row gap-4 bg-transparent items-center box-border;
|
||||
}
|
||||
|
||||
button.clear {
|
||||
@apply bg-transparent text-ucla-blue border-none outline-none font-bold text-regular;
|
||||
}
|
||||
|
||||
.hint {
|
||||
@apply text-brand text-x-small font-bold;
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
ui-select,
|
||||
input {
|
||||
@apply font-bold flex-grow;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='password'],
|
||||
input[type='tel'],
|
||||
input[type='mail'] {
|
||||
@apply outline-none border-none font-bold text-regular;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-white text-black;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='checkbox'] {
|
||||
@apply appearance-none bg-current;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
mask: url('/assets/checkbox.svg') no-repeat;
|
||||
|
||||
&:checked {
|
||||
mask: url('/assets/checkbox_checked.svg') no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host[type='radio'] ::ng-deep,
|
||||
:host[type='checkbox'] ::ng-deep {
|
||||
input {
|
||||
@apply flex-grow-0 m-0;
|
||||
}
|
||||
|
||||
input:checked ~ label {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
label {
|
||||
@apply flex-grow -ml-2;
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
@apply flex flex-row-reverse gap-4 flex-grow items-center;
|
||||
}
|
||||
|
||||
:host[type='radio'],
|
||||
:host[type='checkbox'] {
|
||||
.input-wrapper {
|
||||
@apply flex-row;
|
||||
}
|
||||
}
|
||||
|
||||
:host[variant='default'] {
|
||||
::ng-deep {
|
||||
input[type='text'],
|
||||
input[type='password'],
|
||||
input[type='tel'],
|
||||
input[type='mail'],
|
||||
ui-select {
|
||||
@apply border-t-0 border-l-0 border-r-0 border-solid pt-px-20 pb-px-10;
|
||||
border-bottom-width: 2px;
|
||||
border-color: #e1ebf5;
|
||||
}
|
||||
|
||||
input.ng-touched.ng-invalid,
|
||||
ui-select.ng-touched.ng-invalid {
|
||||
@apply border-brand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host[variant='default']:not([type='radio']):not([type='checkbox']) {
|
||||
@apply relative mt-px-20;
|
||||
|
||||
label {
|
||||
@apply absolute left-0 font-bold text-cta-l pointer-events-none text-ucla-blue;
|
||||
top: 20px;
|
||||
transition: 250ms all ease-in-out;
|
||||
}
|
||||
|
||||
.hint {
|
||||
@apply absolute right-0;
|
||||
bottom: -21px;
|
||||
}
|
||||
|
||||
// ::ng-deep input:focus ~ label,
|
||||
// ::ng-deep input:not(.empty) ~ label,
|
||||
// ::ng-deep ui-select.toggled ~ label,
|
||||
// ::ng-deep ui-select:not(.empty) ~ label {
|
||||
// @apply top-0 text-small font-semibold;
|
||||
// // font-size: 10px;
|
||||
// }
|
||||
|
||||
.input-wrapper.focused,
|
||||
.input-wrapper:not(.empty) {
|
||||
label {
|
||||
@apply top-0 text-small font-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
OnDestroy,
|
||||
HostBinding,
|
||||
} from '@angular/core';
|
||||
import { NgControl } from '@angular/forms';
|
||||
import { combineLatest, Subscription } from 'rxjs';
|
||||
import { UiFormControlDirective } from './ui-form-control.directive';
|
||||
|
||||
@@ -21,24 +22,35 @@ export class UiFormControlComponent implements AfterContentInit, OnDestroy {
|
||||
private subscriptions = new Subscription();
|
||||
|
||||
@Input()
|
||||
@HostBinding('class')
|
||||
@HostBinding('attr.variant')
|
||||
variant: 'inline' | 'default' = 'default';
|
||||
|
||||
@HostBinding('attr.type')
|
||||
get controlType() {
|
||||
return this.uiControl?.type;
|
||||
}
|
||||
|
||||
@Input()
|
||||
label: string;
|
||||
|
||||
@Input()
|
||||
clearable = true;
|
||||
requiredMark: string;
|
||||
|
||||
@Input()
|
||||
clearable = false;
|
||||
|
||||
@ContentChild(NgControl, { read: NgControl })
|
||||
ngControl: NgControl;
|
||||
|
||||
@ContentChild(UiFormControlDirective, { read: UiFormControlDirective })
|
||||
control: UiFormControlDirective<any>;
|
||||
uiControl: UiFormControlDirective<any>;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.control) {
|
||||
if (this.ngControl) {
|
||||
this.subscriptions.add(
|
||||
this.control.changes.subscribe(() => {
|
||||
combineLatest([this.ngControl.control.statusChanges, this.ngControl.control.valueChanges]).subscribe((value) => {
|
||||
this.cdr.markForCheck();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { Directive, EventEmitter } from '@angular/core';
|
||||
import { NgControl } from '@angular/forms';
|
||||
|
||||
@Directive()
|
||||
export abstract class UiFormControlDirective<T> {
|
||||
changes = new EventEmitter<string>();
|
||||
focused = new EventEmitter<boolean>();
|
||||
|
||||
value: T | null;
|
||||
abstract type: string;
|
||||
|
||||
valueChange = new EventEmitter<T>();
|
||||
|
||||
disabled?: boolean;
|
||||
|
||||
disabledChange = new EventEmitter<T>();
|
||||
abstract get valueEmpty(): boolean;
|
||||
|
||||
abstract clear(): void;
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { UiFormControlComponent } from './ui-form-control.component';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiFormControlFirstErrorPipe } from './ui-form-first-error.pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiIconModule],
|
||||
exports: [UiFormControlComponent],
|
||||
declarations: [UiFormControlComponent],
|
||||
declarations: [UiFormControlComponent, UiFormControlFirstErrorPipe],
|
||||
})
|
||||
export class UiFormControlModule {}
|
||||
|
||||
19
apps/ui/form-control/src/lib/ui-form-first-error.pipe.ts
Normal file
19
apps/ui/form-control/src/lib/ui-form-first-error.pipe.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { ValidationErrors } from '@angular/forms';
|
||||
@Pipe({
|
||||
name: 'uiFormControlFirstError',
|
||||
})
|
||||
export class UiFormControlFirstErrorPipe implements PipeTransform {
|
||||
transform(errors: ValidationErrors, label: string): string {
|
||||
console.log(errors);
|
||||
if (errors) {
|
||||
const error = Object.keys(errors)[0];
|
||||
|
||||
switch (error) {
|
||||
case 'required':
|
||||
return `${label} wird benötigt`;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,9 @@
|
||||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { Directive, ElementRef, forwardRef, HostBinding, HostListener, Input, Renderer2, Self } from '@angular/core';
|
||||
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { UiFormControlDirective } from '@ui/form-control';
|
||||
|
||||
@Directive({
|
||||
selector: 'input[uiInput]',
|
||||
selector: 'input[uiInput]:not([type=radio]):not([type=checkbox])',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
@@ -28,71 +17,80 @@ import { UiFormControlDirective } from '@ui/form-control';
|
||||
],
|
||||
exportAs: 'uiInput',
|
||||
})
|
||||
export class UiInputDirective implements UiFormControlDirective<any>, OnChanges, ControlValueAccessor {
|
||||
@Output()
|
||||
changes = new EventEmitter<string>();
|
||||
|
||||
export class UiInputDirective extends UiFormControlDirective<any> implements ControlValueAccessor {
|
||||
@Input()
|
||||
@HostBinding('value')
|
||||
value: any;
|
||||
@HostBinding('attr.type')
|
||||
type: string;
|
||||
|
||||
@Output()
|
||||
valueChange = new EventEmitter<any>();
|
||||
private currentValue: any;
|
||||
|
||||
@Input()
|
||||
@HostBinding('disabled')
|
||||
disabled?: boolean;
|
||||
get value() {
|
||||
return this.currentValue;
|
||||
}
|
||||
|
||||
@Output()
|
||||
disabledChange = new EventEmitter<any>();
|
||||
get valueEmpty(): boolean {
|
||||
return !!this.value;
|
||||
}
|
||||
|
||||
private onChange = (value: any) => {};
|
||||
|
||||
private onTouched = () => {};
|
||||
|
||||
constructor(private elementRef: ElementRef) {}
|
||||
|
||||
ngOnChanges({ disabled }: SimpleChanges): void {
|
||||
if (disabled) {
|
||||
this.changes.emit('disabled');
|
||||
}
|
||||
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
|
||||
super();
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
console.log(obj);
|
||||
this.value = obj || '';
|
||||
this.changes.emit('value');
|
||||
this.setValue(obj, false);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
this.changes.emit('disabled');
|
||||
if (isDisabled) {
|
||||
this.renderer.setAttribute(this.elementRef.nativeElement, 'disabled', '');
|
||||
} else {
|
||||
this.renderer.removeAttribute(this.elementRef.nativeElement, 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.setValue('');
|
||||
this.setValue(undefined);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
setTimeout(() => {
|
||||
this.elementRef?.nativeElement?.focus();
|
||||
this.elementRef?.nativeElement?.click().focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@HostListener('keyup', ['$event.target.value'])
|
||||
setValue(value: any): void {
|
||||
console.log(value);
|
||||
if (value !== this.value) {
|
||||
this.value = value || '';
|
||||
this.onChange(this.value);
|
||||
this.valueChange.next(this.value);
|
||||
this.changes.emit('value');
|
||||
setValue(value: any, emitEvent = true) {
|
||||
if (this.value !== value) {
|
||||
this.currentValue = value;
|
||||
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.value);
|
||||
|
||||
if (emitEvent) {
|
||||
this.onChange(value);
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('focus')
|
||||
onFocus() {
|
||||
this.focused.emit(true);
|
||||
}
|
||||
|
||||
@HostListener('blur')
|
||||
onBlur() {
|
||||
this.onTouched();
|
||||
this.focused.emit(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { UiInputDirective } from './ui-input.directive';
|
||||
import { UiRadioInputDirective } from './ui-radio-input.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [UiInputDirective],
|
||||
declarations: [UiInputDirective],
|
||||
imports: [CommonModule],
|
||||
exports: [UiInputDirective, UiRadioInputDirective],
|
||||
declarations: [UiInputDirective, UiRadioInputDirective],
|
||||
providers: [],
|
||||
})
|
||||
export class UiInputModule {}
|
||||
|
||||
82
apps/ui/input/src/lib/ui-radio-input.directive.ts
Normal file
82
apps/ui/input/src/lib/ui-radio-input.directive.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Directive, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Input, Output, Renderer2 } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { UiFormControlDirective } from '@ui/form-control';
|
||||
|
||||
@Directive({
|
||||
selector: 'input[uiInput][type="radio"], input[uiInput][type="checkbox"]',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => UiRadioInputDirective),
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: UiFormControlDirective,
|
||||
useExisting: UiRadioInputDirective,
|
||||
},
|
||||
],
|
||||
exportAs: 'uiInput',
|
||||
})
|
||||
export class UiRadioInputDirective extends UiFormControlDirective<any> implements ControlValueAccessor {
|
||||
@Input()
|
||||
value: string;
|
||||
|
||||
@Input()
|
||||
@HostBinding('attr.type')
|
||||
type: string;
|
||||
|
||||
private onChange = (value: any) => {};
|
||||
private onTouched = () => {};
|
||||
|
||||
get valueEmpty(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
|
||||
super();
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.check(this.value === obj, false);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
if (isDisabled) {
|
||||
this.renderer.setAttribute(this.elementRef.nativeElement, 'disabled', '');
|
||||
} else {
|
||||
this.renderer.removeAttribute(this.elementRef.nativeElement, 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.check(false);
|
||||
}
|
||||
|
||||
focus(): void {}
|
||||
|
||||
@HostListener('click')
|
||||
click(): void {
|
||||
this.check(true);
|
||||
}
|
||||
|
||||
check(value: boolean, emitEvent = true) {
|
||||
if (value) {
|
||||
this.renderer.setAttribute(this.elementRef.nativeElement, 'checked', '');
|
||||
} else {
|
||||
this.renderer.removeAttribute(this.elementRef.nativeElement, 'checked');
|
||||
}
|
||||
|
||||
if (emitEvent) {
|
||||
this.onChange(this.value);
|
||||
}
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
{{ label }}
|
||||
<button #selectButton (click)="select()" tabIndex="-1">
|
||||
{{ label }}
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, HostListener, EventEmitter, Output } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, HostListener, EventEmitter, Output, ElementRef, ViewChild } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-select-option',
|
||||
@@ -15,16 +15,29 @@ export class UiSelectOptionComponent {
|
||||
|
||||
onSelect = (value: any) => {};
|
||||
|
||||
constructor() {}
|
||||
@ViewChild('selectButton', { read: ElementRef })
|
||||
selectButton: ElementRef;
|
||||
|
||||
get focused() {
|
||||
const host: HTMLElement = this.elementRef.nativeElement;
|
||||
return !!host.querySelector(':focus');
|
||||
}
|
||||
|
||||
constructor(private elementRef: ElementRef) {}
|
||||
|
||||
registerOnSelect(fn: any) {
|
||||
this.onSelect = fn;
|
||||
}
|
||||
|
||||
@HostListener('click')
|
||||
select() {
|
||||
if (typeof this.onSelect === 'function') {
|
||||
this.onSelect.call(null, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
setTimeout(() => {
|
||||
this.selectButton.nativeElement.focus();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="ui-input-wrapper">
|
||||
<div class="ui-select-value" (click)="toggle()">{{ label }}</div>
|
||||
<div class="ui-select-value">{{ label }}</div>
|
||||
<button class="ui-select-toggle" (click)="toggle()" [disabled]="disabled">
|
||||
<ui-icon icon="arrow_head"></ui-icon>
|
||||
</button>
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
@apply flex flex-col box-border text-regular;
|
||||
}
|
||||
|
||||
:host:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.ui-input-wrapper {
|
||||
@apply flex flex-row bg-white px-px-10;
|
||||
@apply flex flex-row bg-white;
|
||||
}
|
||||
|
||||
.ui-select-value {
|
||||
@apply flex-grow;
|
||||
@apply flex-grow text-cta-l;
|
||||
height: 21px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.ui-select-toggle {
|
||||
@@ -31,6 +37,7 @@
|
||||
transition: 250ms all ease-in-out;
|
||||
@apply z-dropdown overflow-auto left-0 right-0;
|
||||
max-height: 0rem;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
:host.toggled .ui-select-dropdown-wrapper .ui-select-options {
|
||||
@@ -38,13 +45,14 @@
|
||||
}
|
||||
|
||||
.ui-select-dropdown-wrapper .ui-select-options {
|
||||
@apply flex flex-col absolute bg-white shadow-xl rounded-b-card;
|
||||
@apply flex flex-col absolute bg-white shadow-card rounded-card;
|
||||
}
|
||||
|
||||
:host ::ng-deep ui-select-option {
|
||||
@apply py-px-10 px-px-10 cursor-pointer;
|
||||
:host ::ng-deep ui-select-option button {
|
||||
@apply py-px-10 px-px-10 cursor-pointer w-full text-left text-regular bg-white border-0 outline-none font-bold;
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus {
|
||||
@apply bg-glitter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,9 @@ import {
|
||||
AfterContentInit,
|
||||
ChangeDetectorRef,
|
||||
forwardRef,
|
||||
EventEmitter,
|
||||
Output,
|
||||
Self,
|
||||
Optional,
|
||||
ElementRef,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
HostListener,
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { UiFormControlDirective } from '@ui/form-control';
|
||||
import { UiSelectOptionComponent } from './ui-select-option.component';
|
||||
|
||||
@@ -26,67 +20,71 @@ import { UiSelectOptionComponent } from './ui-select-option.component';
|
||||
styleUrls: ['ui-select.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => UiSelectComponent),
|
||||
multi: true,
|
||||
},
|
||||
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => UiSelectComponent), multi: true },
|
||||
{
|
||||
provide: UiFormControlDirective,
|
||||
useExisting: UiSelectComponent,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class UiSelectComponent implements UiFormControlDirective<any>, ControlValueAccessor, AfterContentInit, OnChanges {
|
||||
@Output()
|
||||
changes = new EventEmitter<string>();
|
||||
export class UiSelectComponent extends UiFormControlDirective<any> implements ControlValueAccessor, AfterContentInit {
|
||||
readonly type = 'ui-select';
|
||||
|
||||
@Input()
|
||||
value: any;
|
||||
|
||||
@Output()
|
||||
valueChange = new EventEmitter<any>();
|
||||
|
||||
get label() {
|
||||
return this.options.find((o) => o.value === this.value)?.label;
|
||||
}
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
@Output()
|
||||
disabledChange = new EventEmitter<boolean>();
|
||||
disabled = false;
|
||||
|
||||
@ContentChildren(UiSelectOptionComponent, { read: UiSelectOptionComponent })
|
||||
options: QueryList<UiSelectOptionComponent>;
|
||||
|
||||
@Input()
|
||||
get valueEmpty(): boolean {
|
||||
return !this.value;
|
||||
}
|
||||
|
||||
@HostBinding('class.toggled')
|
||||
toggled: boolean;
|
||||
toggled = false;
|
||||
|
||||
private onChange = (value: any) => {};
|
||||
private onTouched = () => {};
|
||||
onChange = (_: any) => {};
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
onTouched = () => {};
|
||||
|
||||
ngOnChanges({ disabled }: SimpleChanges): void {
|
||||
if (disabled && disabled.currentValue) {
|
||||
this.close();
|
||||
}
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
super();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.setValue(undefined);
|
||||
}
|
||||
|
||||
focus() {
|
||||
@HostListener('focus')
|
||||
focus(): void {
|
||||
this.open();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.registerOptionsSelect();
|
||||
this.options.changes.subscribe((_) => {
|
||||
this.registerOptionsSelect();
|
||||
});
|
||||
}
|
||||
|
||||
private registerOptionsSelect() {
|
||||
this.options.forEach((option) => {
|
||||
option.registerOnSelect((value) => {
|
||||
this.setValue(value);
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.value = obj;
|
||||
this.cdr.markForCheck();
|
||||
this.changes.emit('value');
|
||||
this.setValue(obj, false);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
@@ -99,58 +97,60 @@ export class UiSelectComponent implements UiFormControlDirective<any>, ControlVa
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (isDisabled) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
setValue(value: any, emitEvent: boolean = true) {
|
||||
if (this.value !== value) {
|
||||
this.value = value;
|
||||
|
||||
if (emitEvent) {
|
||||
this.onChange(this.value);
|
||||
}
|
||||
}
|
||||
this.cdr.markForCheck();
|
||||
this.changes.emit('disabled');
|
||||
|
||||
this.onTouched();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (!this.disabled) {
|
||||
this.toggled = !this.toggled;
|
||||
this.cdr.markForCheck();
|
||||
this.changes.emit('toggled');
|
||||
if (this.toggled) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.toggled = true;
|
||||
this.options.first?.focus();
|
||||
this.cdr.markForCheck();
|
||||
this.changes.emit('toggled');
|
||||
}
|
||||
|
||||
close() {
|
||||
this.toggled = false;
|
||||
this.cdr.markForCheck();
|
||||
this.changes.emit('toggled');
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.registerOptionsSelect();
|
||||
|
||||
this.options.changes.subscribe((_) => {
|
||||
this.registerOptionsSelect();
|
||||
});
|
||||
}
|
||||
|
||||
setValue(value: any) {
|
||||
if (value !== this.value) {
|
||||
this.value = value;
|
||||
this.onChange(value);
|
||||
this.valueChange.next(value);
|
||||
this.changes.emit('value');
|
||||
}
|
||||
this.onTouched();
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
private registerOptionsSelect() {
|
||||
this.options.forEach((option) => {
|
||||
option.registerOnSelect((value) => {
|
||||
this.setValue(value);
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
@HostListener('keyup', ['$event'])
|
||||
keyup(event: KeyboardEvent) {
|
||||
let optionIndex = this.options.toArray().findIndex((o) => o.focused);
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
optionIndex--;
|
||||
if (optionIndex < 0) {
|
||||
return;
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
optionIndex++;
|
||||
if (this.options.length - 1 < optionIndex) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.toArray()[optionIndex]?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ module.exports = {
|
||||
},
|
||||
extend: {
|
||||
fontSize: {
|
||||
'x-small': '12px',
|
||||
small: '14px',
|
||||
regular: '16px',
|
||||
'cta-l': '18px',
|
||||
'card-heading': '22px',
|
||||
|
||||
Reference in New Issue
Block a user