more
This commit is contained in:
parent
359e8b3f44
commit
26eaa39411
@ -1,4 +1,5 @@
|
|||||||
<app-progress-stepper></app-progress-stepper>
|
<app-progress-stepper
|
||||||
|
[steps]="['Bla', 'Noch ein Test', 'Abgeschlossen', '2', '4', '5', '2', '4', '5']"></app-progress-stepper>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Textbox:
|
Textbox:
|
||||||
|
@ -1,56 +1,64 @@
|
|||||||
<div class="step-progress">
|
<div class="step-progress">
|
||||||
<div class="container" #container>
|
<div class="container" #container>
|
||||||
@for (item of [{status: 'completed'}, {status: 'in progress'}, test]; track item; let i =
|
@for (item of steps; track item; let index = $index) {
|
||||||
$index) {
|
<div class="check-box" [class]="{
|
||||||
<ng-container>
|
'complete': index < status.progressIndex,
|
||||||
<div class="check-box" [ngClass]="{
|
'active': index === status.progressIndex && status.progressStatus === STATUS.InProgress,
|
||||||
complete: item.status === 'completed',
|
'canceled': index === status.progressIndex && status.progressStatus === STATUS.Canceled,
|
||||||
active: item.status === 'in progress'
|
'selectable': index <= status.progressIndex
|
||||||
}">
|
}" [class.first]="index === 0">
|
||||||
<!-- <svg id="checkbox" viewBox="0 0 100 100">
|
<div [class]="{
|
||||||
<circle class="circle" cx="50.5" cy="49" r="45" />
|
'complete': index < status.progressIndex + 1,
|
||||||
@if (item.status == 'in progress') {
|
'active': index === status.progressIndex + 1 && status.progressStatus === STATUS.InProgress,
|
||||||
<circle class="inner-circle" cx="50.5" cy="49" r="17" />
|
'canceled': index === status.progressIndex + 1 && status.progressStatus === STATUS.Canceled,
|
||||||
}
|
}" class="progress-line">
|
||||||
<polyline class="check" points="28.5,51.9 41.9,65.3 72.5,32.8 " />
|
<div class="progress-percent" [style.--progress]="getProgress"></div>
|
||||||
</svg> -->
|
|
||||||
|
|
||||||
<svg id="checkbox" viewBox="0 0 100 100">
|
|
||||||
@if (selectedIndex === i) {
|
|
||||||
<circle class="selection-circle" cx="50.5" cy="49" r="40" stroke-width="8" fill="none" />
|
|
||||||
}
|
|
||||||
<circle class="circle" cx="50.5" cy="49" r="34" />
|
|
||||||
@if (item.status == 'in progress') {
|
|
||||||
<circle class="inner-circle" cx="50.5" cy="49" r="17" />
|
|
||||||
}
|
|
||||||
<polyline class="check" points="28.5,51.9 41.9,65.3 72.5,32.8 " />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<div [ngClass]="{
|
|
||||||
complete: item.status === 'completed',
|
|
||||||
active: item.status === 'in progress'
|
|
||||||
}" class="progress-line">
|
|
||||||
<div class="progress-percent"></div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<span class="step">step {{ i + 1 }}</span>
|
|
||||||
<span class="progress-info" [ngClass]="{
|
|
||||||
'completed': item.status === 'completed',
|
|
||||||
'in-progress': item.status === 'in progress'
|
|
||||||
}">{{ item.status }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
<div class="hover-box" (click)="selectStep(index)">
|
||||||
|
<svg id="checkbox" viewBox="0 0 100 100">
|
||||||
|
<circle class="selection-circle" [class.selected]="selectedIndex === index" cx="50.5" cy="49" r="40"
|
||||||
|
stroke-width="4" fill="none" />
|
||||||
|
<circle class="circle" cx="50.5" cy="49" r="34" />
|
||||||
|
@if (index === status.progressIndex) {
|
||||||
|
<circle class="inner-circle" cx="50.5" cy="49" r="17" />
|
||||||
|
}
|
||||||
|
<!-- completed indicator -->
|
||||||
|
<polyline class="check" points="28.5,51.9 41.9,65.3 72.5,32.8 " />
|
||||||
|
<!-- failed indicator -->
|
||||||
|
<polyline class="fail" points="30.5,28 70.5,67" />
|
||||||
|
<polyline class="fail" points="70.5,28 30.5,67" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info" (click)="selectStep(index)">
|
||||||
|
<span class="step">{{ item }}</span>
|
||||||
|
<span class="progress-info" [class]="{
|
||||||
|
'completed': index < status.progressIndex,
|
||||||
|
'in-progress': index === status.progressIndex && status.progressStatus === STATUS.InProgress,
|
||||||
|
'canceled': index === status.progressIndex && status.progressStatus === STATUS.Canceled,
|
||||||
|
'never-called': index > status.progressIndex && status.progressStatus === STATUS.Canceled,
|
||||||
|
}">
|
||||||
|
<span>{{ statusToLabel(indexStatus(index)) }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button style="margin: .25rem; padding: 1rem"
|
||||||
<div (click)="selectedIndex = selectedIndex + 1">next</div>
|
(click)="status.progressIndex = status.progressIndex +1; selectedIndex = selectedIndex + 1">Fertigstellen</button>
|
||||||
<div (click)="selectedIndex = selectedIndex - 1">prev</div>
|
<button style="margin: .25rem; padding: 1rem" (click)="status.progressIndex = status.progressIndex -1">Fertigstellen
|
||||||
|
zurück</button>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button (click)="test.status = 'open'">open</button>
|
<!-- <button (click)="test.status = 'open'">open</button> -->
|
||||||
<button (click)="test.status = 'in progress'">progress</button>
|
<button style="margin: .25rem; padding: 1rem" (click)="status.progressStatus= STATUS.InProgress">progress</button>
|
||||||
<button (click)="test.status = 'completed'">complete</button>
|
<button style="margin: .25rem; padding: 1rem" (click)="status.progressStatus= STATUS.Canceled">Failed</button>
|
||||||
|
<button style="margin: .25rem; padding: 1rem"
|
||||||
|
(click)="status.progressPercent = status.progressPercent + 10">+</button>
|
||||||
|
<button style="margin: .25rem; padding: 1rem"
|
||||||
|
(click)="status.progressPercent = status.progressPercent - 10">-</button>
|
||||||
|
<button style="margin: .25rem; padding: 1rem"
|
||||||
|
(click)="status.progressStatus= STATUS.InProgress; status.progressIndex=0; status.progressPercent=50; selectedIndex=0">Reset</button>
|
||||||
</div>
|
</div>
|
@ -1,3 +1,11 @@
|
|||||||
|
$checkboxWidth: 33px;
|
||||||
|
$checkboxHeight: 33px;
|
||||||
|
|
||||||
|
$color-in-progress: #234dc2;
|
||||||
|
$color-completed: #23c274;
|
||||||
|
$color-canceled: #c23323;
|
||||||
|
$color-selected-step: rebeccapurple;
|
||||||
|
|
||||||
.step-progress {
|
.step-progress {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@ -9,12 +17,12 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
svg#checkbox {
|
svg#checkbox {
|
||||||
width: 33px;
|
position: relative;
|
||||||
height: 33px;
|
width: $checkboxWidth;
|
||||||
stroke: #23c274;
|
height: $checkboxHeight;
|
||||||
|
stroke: $color-completed;
|
||||||
stroke-width: 6;
|
stroke-width: 6;
|
||||||
|
|
||||||
.circle {
|
.circle {
|
||||||
@ -36,12 +44,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selection-circle {
|
.selection-circle {
|
||||||
// stroke-dasharray: 320;
|
stroke-dasharray: 320;
|
||||||
// stroke-dashoffset: 320;
|
stroke-dashoffset: 320;
|
||||||
stroke: rebeccapurple;
|
stroke: $color-selected-step;
|
||||||
transition:
|
transition:
|
||||||
stroke-dashoffset 0.5s,
|
stroke-dashoffset 0.5s,
|
||||||
fill 0.5s 0.3s cubic-bezier(0.45, 0, 0.55, 1);
|
fill 0.5s 0.3s cubic-bezier(0.45, 0, 0.55, 1);
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.check {
|
.check {
|
||||||
@ -51,6 +63,13 @@
|
|||||||
fill: none;
|
fill: none;
|
||||||
transition: all 0.5s 0.5s cubic-bezier(0.45, 0, 0.55, 1);
|
transition: all 0.5s 0.5s cubic-bezier(0.45, 0, 0.55, 1);
|
||||||
}
|
}
|
||||||
|
.fail {
|
||||||
|
stroke-dasharray: 70;
|
||||||
|
stroke-dashoffset: 70;
|
||||||
|
stroke: #fff;
|
||||||
|
fill: none;
|
||||||
|
transition: all 0.5s 0.5s cubic-bezier(0.45, 0, 0.55, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,9 +79,48 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&.selectable {
|
||||||
|
.hover-box:hover,
|
||||||
|
.hover-box:has(+ .info:hover) {
|
||||||
|
&::before {
|
||||||
|
$hoverOffset: 7px;
|
||||||
|
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background: #000;
|
||||||
|
top: calc($hoverOffset / -2);
|
||||||
|
left: calc($hoverOffset / -2);
|
||||||
|
width: calc($checkboxWidth + $hoverOffset);
|
||||||
|
height: calc($checkboxHeight + $hoverOffset);
|
||||||
|
|
||||||
|
border-radius: calc($hoverOffset * 0.5);
|
||||||
|
z-index: 20;
|
||||||
|
opacity: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-box {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.first {
|
||||||
|
flex-grow: 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -40px;
|
bottom: -30px;
|
||||||
|
right: 0;
|
||||||
|
text-align: right;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: max-content;
|
grid-template-columns: max-content;
|
||||||
|
|
||||||
@ -71,23 +129,38 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
padding: 2px 10px;
|
|
||||||
transform: translateY(6px);
|
transform: translateY(6px);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
background: #eee;
|
transition:
|
||||||
border-radius: 20px;
|
opacity 0.5s,
|
||||||
transition: background-color 0.1s ease-in-out;
|
fill 0.5s 0.3s cubic-bezier(0.45, 0, 0.55, 1);
|
||||||
}
|
|
||||||
|
|
||||||
span.progress-info.in-progress {
|
span {
|
||||||
background-color: #234dc2;
|
padding: 2px 10px;
|
||||||
color: #fff;
|
background: #eee;
|
||||||
}
|
border-radius: 20px;
|
||||||
|
transition: background-color 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
span.progress-info.completed {
|
&.never-called {
|
||||||
background-color: #23c274;
|
opacity: 0;
|
||||||
color: #fff;
|
}
|
||||||
|
|
||||||
|
&.in-progress span {
|
||||||
|
background-color: $color-in-progress;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.completed span {
|
||||||
|
background-color: $color-completed;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.canceled span {
|
||||||
|
background-color: $color-canceled;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span.step {
|
span.step {
|
||||||
@ -102,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress-line {
|
.progress-line {
|
||||||
width: 140px;
|
width: 10px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
@ -116,38 +189,58 @@
|
|||||||
width: 0%;
|
width: 0%;
|
||||||
transition: all 0.5s 0.5s cubic-bezier(0.45, 0, 0.55, 1);
|
transition: all 0.5s 0.5s cubic-bezier(0.45, 0, 0.55, 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.progress-line.active {
|
&.active .progress-percent {
|
||||||
.progress-percent {
|
background: $color-in-progress;
|
||||||
background: #234dc2;
|
// width: 50%;
|
||||||
width: 50%;
|
width: var(--progress);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.progress-line.complete {
|
&.complete .progress-percent {
|
||||||
.progress-percent {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #23c274;
|
background: $color-completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.canceled .progress-percent {
|
||||||
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-box.active {
|
.check-box {
|
||||||
svg#checkbox {
|
&.active {
|
||||||
.circle {
|
svg#checkbox {
|
||||||
fill: #234dc2;
|
.circle {
|
||||||
|
fill: $color-in-progress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.check-box.complete {
|
|
||||||
svg#checkbox {
|
|
||||||
.circle {
|
|
||||||
stroke-dashoffset: 0;
|
|
||||||
fill: #23c274;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check {
|
&.complete {
|
||||||
stroke-dashoffset: 0;
|
svg#checkbox {
|
||||||
|
.circle {
|
||||||
|
// stroke-dashoffset: 0;
|
||||||
|
fill: $color-completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.canceled {
|
||||||
|
svg#checkbox {
|
||||||
|
.circle {
|
||||||
|
fill: $color-canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-circle {
|
||||||
|
r: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fail {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,61 @@
|
|||||||
import { NgClass } from '@angular/common';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { Component } from '@angular/core';
|
|
||||||
|
export enum Status {
|
||||||
|
Open = 'open',
|
||||||
|
InProgress = 'in-progress',
|
||||||
|
Completed = 'completed',
|
||||||
|
Canceled = 'canceled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressStatus {
|
||||||
|
progressIndex: number,
|
||||||
|
progressStatus: Status.InProgress | Status.Canceled,
|
||||||
|
progressPercent: number,
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-progress-stepper',
|
selector: 'app-progress-stepper',
|
||||||
imports: [NgClass],
|
imports: [],
|
||||||
templateUrl: './progress-stepper.component.html',
|
templateUrl: './progress-stepper.component.html',
|
||||||
styleUrl: './progress-stepper.component.scss'
|
styleUrl: './progress-stepper.component.scss'
|
||||||
})
|
})
|
||||||
export class ProgressStepperComponent {
|
export class ProgressStepperComponent {
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
|
STATUS = Status;
|
||||||
|
|
||||||
test = { status: 'Offen' };
|
@Input() steps: string[] = [];
|
||||||
|
@Input() status: ProgressStatus = { progressIndex: 0, progressStatus: Status.Canceled, progressPercent: 50 }
|
||||||
|
|
||||||
|
@Output() selectionChange: EventEmitter<number> = new EventEmitter();
|
||||||
|
|
||||||
|
selectStep(index: number) {
|
||||||
|
if (index <= this.status.progressIndex)
|
||||||
|
this.selectedIndex = index;
|
||||||
|
|
||||||
|
this.selectionChange.emit(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
indexStatus(index: number): Status {
|
||||||
|
if (index < this.status.progressIndex)
|
||||||
|
return Status.Completed;
|
||||||
|
else if (index === this.status.progressIndex)
|
||||||
|
if (this.status.progressStatus === Status.InProgress)
|
||||||
|
return Status.InProgress;
|
||||||
|
else return Status.Canceled;
|
||||||
|
else
|
||||||
|
return Status.Open
|
||||||
|
}
|
||||||
|
|
||||||
|
statusToLabel(status: Status): string {
|
||||||
|
switch (status) {
|
||||||
|
case Status.Open: return 'Offen';
|
||||||
|
case Status.InProgress: return 'In Bearbeitung';
|
||||||
|
case Status.Canceled: return 'Verweigert';
|
||||||
|
case Status.Completed: return 'Fertig';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get getProgress(): string {
|
||||||
|
return `${Math.min(Math.max(this.status.progressPercent, 0), 100)}%`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user