This commit is contained in:
Hlars 2025-03-15 07:44:02 +01:00
parent 359e8b3f44
commit 26eaa39411
4 changed files with 243 additions and 94 deletions

View File

@ -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:

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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)}%`
}
}