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>
|
||||
Textbox:
|
||||
|
@ -1,56 +1,64 @@
|
||||
<div class="step-progress">
|
||||
<div class="container" #container>
|
||||
@for (item of [{status: 'completed'}, {status: 'in progress'}, test]; track item; let i =
|
||||
$index) {
|
||||
<ng-container>
|
||||
<div class="check-box" [ngClass]="{
|
||||
complete: item.status === 'completed',
|
||||
active: item.status === 'in progress'
|
||||
}">
|
||||
<!-- <svg id="checkbox" viewBox="0 0 100 100">
|
||||
<circle class="circle" cx="50.5" cy="49" r="45" />
|
||||
@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> -->
|
||||
|
||||
<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>
|
||||
@for (item of steps; track item; let index = $index) {
|
||||
<div class="check-box" [class]="{
|
||||
'complete': index < status.progressIndex,
|
||||
'active': index === status.progressIndex && status.progressStatus === STATUS.InProgress,
|
||||
'canceled': index === status.progressIndex && status.progressStatus === STATUS.Canceled,
|
||||
'selectable': index <= status.progressIndex
|
||||
}" [class.first]="index === 0">
|
||||
<div [class]="{
|
||||
'complete': index < status.progressIndex + 1,
|
||||
'active': index === status.progressIndex + 1 && status.progressStatus === STATUS.InProgress,
|
||||
'canceled': index === status.progressIndex + 1 && status.progressStatus === STATUS.Canceled,
|
||||
}" class="progress-line">
|
||||
<div class="progress-percent" [style.--progress]="getProgress"></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 (click)="selectedIndex = selectedIndex + 1">next</div>
|
||||
<div (click)="selectedIndex = selectedIndex - 1">prev</div>
|
||||
<button style="margin: .25rem; padding: 1rem"
|
||||
(click)="status.progressIndex = status.progressIndex +1; selectedIndex = selectedIndex + 1">Fertigstellen</button>
|
||||
<button style="margin: .25rem; padding: 1rem" (click)="status.progressIndex = status.progressIndex -1">Fertigstellen
|
||||
zurück</button>
|
||||
|
||||
<div>
|
||||
<button (click)="test.status = 'open'">open</button>
|
||||
<button (click)="test.status = 'in progress'">progress</button>
|
||||
<button (click)="test.status = 'completed'">complete</button>
|
||||
<!-- <button (click)="test.status = 'open'">open</button> -->
|
||||
<button style="margin: .25rem; padding: 1rem" (click)="status.progressStatus= STATUS.InProgress">progress</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>
|
@ -1,3 +1,11 @@
|
||||
$checkboxWidth: 33px;
|
||||
$checkboxHeight: 33px;
|
||||
|
||||
$color-in-progress: #234dc2;
|
||||
$color-completed: #23c274;
|
||||
$color-canceled: #c23323;
|
||||
$color-selected-step: rebeccapurple;
|
||||
|
||||
.step-progress {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@ -9,12 +17,12 @@
|
||||
width: 100%;
|
||||
padding: 30px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
svg#checkbox {
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
stroke: #23c274;
|
||||
position: relative;
|
||||
width: $checkboxWidth;
|
||||
height: $checkboxHeight;
|
||||
stroke: $color-completed;
|
||||
stroke-width: 6;
|
||||
|
||||
.circle {
|
||||
@ -36,12 +44,16 @@
|
||||
}
|
||||
|
||||
.selection-circle {
|
||||
// stroke-dasharray: 320;
|
||||
// stroke-dashoffset: 320;
|
||||
stroke: rebeccapurple;
|
||||
stroke-dasharray: 320;
|
||||
stroke-dashoffset: 320;
|
||||
stroke: $color-selected-step;
|
||||
transition:
|
||||
stroke-dashoffset 0.5s,
|
||||
fill 0.5s 0.3s cubic-bezier(0.45, 0, 0.55, 1);
|
||||
|
||||
&.selected {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.check {
|
||||
@ -51,6 +63,13 @@
|
||||
fill: none;
|
||||
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;
|
||||
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 {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
bottom: -30px;
|
||||
right: 0;
|
||||
text-align: right;
|
||||
display: grid;
|
||||
grid-template-columns: max-content;
|
||||
|
||||
@ -71,23 +129,38 @@
|
||||
font-size: 11px;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
padding: 2px 10px;
|
||||
transform: translateY(6px);
|
||||
font-weight: 500;
|
||||
color: currentColor;
|
||||
background: #eee;
|
||||
border-radius: 20px;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
}
|
||||
transition:
|
||||
opacity 0.5s,
|
||||
fill 0.5s 0.3s cubic-bezier(0.45, 0, 0.55, 1);
|
||||
|
||||
span.progress-info.in-progress {
|
||||
background-color: #234dc2;
|
||||
color: #fff;
|
||||
}
|
||||
span {
|
||||
padding: 2px 10px;
|
||||
background: #eee;
|
||||
border-radius: 20px;
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
span.progress-info.completed {
|
||||
background-color: #23c274;
|
||||
color: #fff;
|
||||
&.never-called {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.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 {
|
||||
@ -102,7 +175,7 @@
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
width: 140px;
|
||||
width: 10px;
|
||||
flex-grow: 1;
|
||||
margin: 0 10px;
|
||||
height: 4px;
|
||||
@ -116,38 +189,58 @@
|
||||
width: 0%;
|
||||
transition: all 0.5s 0.5s cubic-bezier(0.45, 0, 0.55, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-line.active {
|
||||
.progress-percent {
|
||||
background: #234dc2;
|
||||
width: 50%;
|
||||
&.active .progress-percent {
|
||||
background: $color-in-progress;
|
||||
// width: 50%;
|
||||
width: var(--progress);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-line.complete {
|
||||
.progress-percent {
|
||||
&.complete .progress-percent {
|
||||
width: 100%;
|
||||
background: #23c274;
|
||||
background: $color-completed;
|
||||
}
|
||||
|
||||
&.canceled .progress-percent {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.check-box.active {
|
||||
svg#checkbox {
|
||||
.circle {
|
||||
fill: #234dc2;
|
||||
.check-box {
|
||||
&.active {
|
||||
svg#checkbox {
|
||||
.circle {
|
||||
fill: $color-in-progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.check-box.complete {
|
||||
svg#checkbox {
|
||||
.circle {
|
||||
stroke-dashoffset: 0;
|
||||
fill: #23c274;
|
||||
}
|
||||
|
||||
.check {
|
||||
stroke-dashoffset: 0;
|
||||
&.complete {
|
||||
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 } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } 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({
|
||||
selector: 'app-progress-stepper',
|
||||
imports: [NgClass],
|
||||
imports: [],
|
||||
templateUrl: './progress-stepper.component.html',
|
||||
styleUrl: './progress-stepper.component.scss'
|
||||
})
|
||||
export class ProgressStepperComponent {
|
||||
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