Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions modules/ui/src/app/components/stepper/stepper.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<section class="form-container">
<header class="form-header" *ngIf="header">
<ng-container [ngTemplateOutlet]="header"></ng-container>
</header>

<div class="form-content">
<ng-container
[ngTemplateOutlet]="selected ? selected.content : null"></ng-container>
</div>

<footer class="form-footer">
<button
class="form-button-back"
[class.hidden]="backButtonHidden()"
cdkStepperPrevious
mat-button
aria-label="Go to previous step">
<mat-icon fontSet="material-symbols-outlined"> arrow_back </mat-icon>
</button>
<div class="form-steps">
<div
*ngFor="let step of stepsCount; let i = index"
class="form-step"
[class.step-active]="selectedIndex === i"></div>
</div>
<button
class="form-button-forward"
[class.hidden]="forwardButtonHidden()"
cdkStepperNext
mat-button
aria-label="Go to next step">
<mat-icon fontSet="material-symbols-outlined"> arrow_forward </mat-icon>
</button>
</footer>
</section>
83 changes: 83 additions & 0 deletions modules/ui/src/app/components/stepper/stepper.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../theming/colors';

.form-container {
height: 100%;
display: flex;
flex-direction: column;
}

.form-header {
padding: 24px;
}

.form-content {
display: grid;
padding: 24px;
gap: 10px;
overflow: auto;
}

.form-footer {
margin-top: auto;
display: inline-block;
text-align: center;
padding-bottom: 24px;
width: 100%;
}

.form-steps {
display: inline-flex;
text-align: center;
align-items: center;
height: 100%;
}

.form-step {
border: 2px solid $lighter-grey;
width: 4px;
height: 4px;
display: inline-block;
border-radius: 50%;
margin: 0 8px;
&.step-active {
border-color: $secondary;
background: $secondary;
}
}

.form-button-back {
float: left;
}

.form-button-forward {
float: right;
}

.form-button-back,
.form-button-forward {
& mat-icon {
color: $secondary;
width: 24px;
height: 24px;
font-size: 24px;
}

&.hidden {
display: none;
}
}
36 changes: 36 additions & 0 deletions modules/ui/src/app/components/stepper/stepper.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { StepperComponent } from './stepper.component';
import { Component } from '@angular/core';

@Component({
selector: 'app-stepper-bypass',
template:
'<app-stepper [header]="header"></app-stepper>' +
' <ng-template #header> <div>Header</div></ng-template>',
})
class TestStepperComponent {}

describe('StepperComponent', () => {
let component: TestStepperComponent;
let fixture: ComponentFixture<TestStepperComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [StepperComponent],
declarations: [TestStepperComponent],
}).compileComponents();

fixture = TestBed.createComponent(TestStepperComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should have title', () => {
expect(fixture.nativeElement.querySelector('.form-header')).toBeTruthy();
});
});
36 changes: 36 additions & 0 deletions modules/ui/src/app/components/stepper/stepper.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, Input, TemplateRef } from '@angular/core';
import { CdkStepper, CdkStepperModule } from '@angular/cdk/stepper';
import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
import { MatIcon } from '@angular/material/icon';
import { MatButton, MatIconButton } from '@angular/material/button';

@Component({
selector: 'app-stepper',
standalone: true,
imports: [
NgForOf,
NgTemplateOutlet,
CdkStepperModule,
NgIf,
MatIcon,
MatIconButton,
MatButton,
],
templateUrl: './stepper.component.html',
styleUrl: './stepper.component.scss',
providers: [{ provide: CdkStepper, useExisting: StepperComponent }],
})
export class StepperComponent extends CdkStepper {
@Input() header: TemplateRef<HTMLElement> | undefined;
@Input() activeClass = 'active';

stepsCount = [1, 2, 3, 4]; //TODO will be removed when all steps are implemented

forwardButtonHidden() {
return this.selectedIndex === this.stepsCount.length - 1;
}

backButtonHidden() {
return this.selectedIndex === 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<form
class="device-qualification-form"
[formGroup]="deviceQualificationForm"
(submit)="submit()">
<ng-template #header>
<div class="device-qualification-form-header">
<button
(click)="closeForm()"
aria-label="close"
id="device-qualification-form-header-close-button"
class="device-qualification-form-header-close-button"
mat-button>
<mat-icon class="close-button-icon" svgIcon="close"></mat-icon>
</button>
<h2 class="device-qualification-form-header-title">{{ data.title }}</h2>
</div>
</ng-template>

<app-stepper
#cdkStepper
formArrayName="steps"
[linear]="true"
[header]="header">
<cdk-step [editable]="true" formGroupName="0" [stepControl]="first_step">
<mat-form-field appearance="outline" class="manufacturer-field">
<mat-label>Device Manufacturer</mat-label>
<input
class="device-qualification-form-manufacturer"
formControlName="manufacturer"
matInput />
<mat-hint>Please enter device manufacturer name</mat-hint>
<mat-error
*ngIf="manufacturer.hasError('invalid_format')"
role="alert"
aria-live="assertive">
<span
>Please, check. The manufacturer name must be a maximum of 28
characters. Only letters, numbers, and accented letters are
permitted.</span
>
</mat-error>
<mat-error *ngIf="manufacturer.errors?.['required']">
<span>Device Manufacturer is <strong>required</strong></span>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="model-field">
<mat-label>Device Model</mat-label>
<input
class="device-qualification-form-model"
formControlName="model"
matInput />
<mat-hint>Please enter device name</mat-hint>
<mat-error
*ngIf="model.hasError('invalid_format')"
role="alert"
aria-live="assertive">
<span
>Please, check. The device model name must be a maximum of 28
characters. Only letters, numbers, and accented letters are
permitted.</span
>
</mat-error>
<mat-error *ngIf="model.errors?.['required']">
<span>Device Model is <strong>required</strong></span>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>MAC address</mat-label>
<input
class="device-qualification-form-mac-address"
formControlName="mac_addr"
matInput
[dropSpecialCharacters]="false"
[showMaskTyped]="true"
[specialCharacters]="[':']"
mask="AA:AA:AA:AA:AA:AA"
shownMaskExpression="__:__:__:__:__:__"
type="text" />
<mat-hint>Please enter MAC address</mat-hint>
<mat-error
*ngIf="
mac_addr.errors?.['required'] && !mac_addr.errors?.['pattern']
">
<span>MAC address is <strong>required</strong></span>
</mat-error>
<mat-error
*ngIf="mac_addr.errors?.['pattern']"
class="device-form-mac-address-error">
<span
>Please, check. A MAC address consists of 12 hexadecimal digits (0
to 9, a to f, or A to F).</span
>
</mat-error>
<mat-error *ngIf="mac_addr.errors?.['has_same_mac_address']">
<span
>This MAC address is already used for another device in the
repository.</span
>
</mat-error>
</mat-form-field>

<mat-label class="device-qualification-form-journey-label"
>Please, select the testing journey for device</mat-label
>
<input matInput style="display: none" />

<mat-radio-group formControlName="testing_journey">
<mat-radio-button
[value]="0"
class="device-qualification-form-journey-button">
<span class="device-qualification-form-journey-button-label">
🛡️ Device Qualification
</span>
</mat-radio-button>

<mat-radio-button
[value]="1"
class="device-qualification-form-journey-button">
<span class="device-qualification-form-journey-button-label">
🚀 Pilot Assessment
</span>
</mat-radio-button>
</mat-radio-group>

<app-device-tests
class="device-qualification-form-test-modules-container"
[deviceForm]="first_step"
[deviceTestModules]="data.device?.test_modules"
[testModules]="testModules">
</app-device-tests>
</cdk-step>

<cdk-step [editable]="true" formGroupName="1" [stepControl]="second_step">
Second step
</cdk-step>
</app-stepper>
</form>
Loading