init commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Global Progress Bar Component Styles
|
||||
* Professional Angular Resume Builder - GitHub-style Progress Bar
|
||||
*
|
||||
* @author David Valera Melendez <david@valera-melendez.de>
|
||||
* @created 2025-08-08
|
||||
* @location Made in Germany 🇩🇪
|
||||
*/
|
||||
|
||||
.global-progress-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background: var(--gradient-progress);
|
||||
background-size: 200% 100%;
|
||||
z-index: 9999;
|
||||
width: 0%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out, width 0.3s ease-out;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.global-progress-bar.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* GitHub-style alternative */
|
||||
.global-progress-bar::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
var(--color-surface-overlay-medium) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: loading-shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes loading-shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Professional blue theme */
|
||||
.global-progress-bar {
|
||||
background: linear-gradient(90deg,
|
||||
var(--color-primary),
|
||||
var(--color-primary),
|
||||
var(--color-blue-400),
|
||||
var(--color-primary),
|
||||
var(--color-primary)
|
||||
);
|
||||
background-size: 300% 100%;
|
||||
box-shadow: 0 0 10px var(--color-blue-glow);
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.global-progress-bar {
|
||||
background: linear-gradient(90deg,
|
||||
var(--color-blue-300),
|
||||
var(--color-blue-200),
|
||||
var(--color-blue-100),
|
||||
var(--color-blue-200),
|
||||
var(--color-blue-300)
|
||||
);
|
||||
box-shadow: 0 0 10px var(--color-blue-200-glow);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.global-progress-bar {
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!--
|
||||
Global Progress Bar Component Template
|
||||
Professional Angular Resume Builder - GitHub-style Progress Bar
|
||||
|
||||
@author David Valera Melendez <david@valera-melendez.de>
|
||||
@created 2025-08-08
|
||||
@location Made in Germany 🇩🇪
|
||||
-->
|
||||
|
||||
<div
|
||||
class="global-progress-bar"
|
||||
[class.active]="isLoading"
|
||||
[style.width.%]="progress">
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Global Progress Bar Component
|
||||
* Professional Angular Resume Builder - GitHub-style Progress Bar
|
||||
*
|
||||
* @author David Valera Melendez <david@valera-melendez.de>
|
||||
* @created 2025-08-08
|
||||
* @location Made in Germany 🇩🇪
|
||||
*/
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { LoadingService } from '../../../services/loading.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-global-progress-bar',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './global-progress-bar.component.html',
|
||||
styleUrls: ['./global-progress-bar.component.css']
|
||||
})
|
||||
export class GlobalProgressBarComponent implements OnInit, OnDestroy {
|
||||
isLoading = false;
|
||||
progress = 0;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private loadingService: LoadingService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Subscribe to global loading state
|
||||
this.loadingService.globalLoading$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(loading => {
|
||||
this.isLoading = loading;
|
||||
});
|
||||
|
||||
// Subscribe to progress updates
|
||||
this.loadingService.progress$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(progress => {
|
||||
this.progress = progress;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Loading Button Component Styles
|
||||
* Professional Angular Resume Builder - Enterprise Button with Spinner
|
||||
*
|
||||
* @author David Valera Melendez <david@valera-melendez.de>
|
||||
* @created 2025-08-08
|
||||
* @location Made in Germany 🇩🇪
|
||||
*/
|
||||
|
||||
.button-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
min-height: 24px;
|
||||
transition: var(--transition-slow);
|
||||
}
|
||||
|
||||
.button-content.loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
transition: var(--transition-slow);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.button-text.loading-text {
|
||||
margin-left: 32px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.icon-margin {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Professional button styles */
|
||||
button {
|
||||
min-width: 120px;
|
||||
height: 44px;
|
||||
border-radius: var(--border-radius-md);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.5px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button:not([disabled]):hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px var(--color-shadow-light);
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Ripple effect */
|
||||
button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface-overlay-light);
|
||||
transition: width 0.6s, height 0.6s, top 0.6s, left 0.6s;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
button:active::before {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* Size variations */
|
||||
.small {
|
||||
min-width: 80px;
|
||||
height: 36px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.large {
|
||||
min-width: 160px;
|
||||
height: 52px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Color themes */
|
||||
.success {
|
||||
background-color: var(--color-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--color-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--color-warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Loading animation */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
animation: pulse 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
button {
|
||||
background-color: var(--color-background-darker);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<!--
|
||||
Loading Button Component Template
|
||||
Professional Angular Resume Builder - Enterprise Button with Spinner
|
||||
|
||||
@author David Valera Melendez <david@valera-melendez.de>
|
||||
@created 2025-08-08
|
||||
@location Made in Germany 🇩🇪
|
||||
-->
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
[color]="color"
|
||||
[disabled]="isLoading || disabled"
|
||||
[class]="buttonClass"
|
||||
(click)="onButtonClick()"
|
||||
type="submit">
|
||||
|
||||
<div class="button-content" [class.loading]="isLoading">
|
||||
<!-- Loading Spinner -->
|
||||
<div class="spinner-container" *ngIf="isLoading">
|
||||
<mat-spinner [diameter]="spinnerSize" [color]="spinnerColor"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<!-- Button Icon -->
|
||||
<mat-icon *ngIf="icon && !isLoading" [class.icon-margin]="text">
|
||||
{{ icon }}
|
||||
</mat-icon>
|
||||
|
||||
<!-- Button Text -->
|
||||
<span class="button-text" [class.loading-text]="isLoading">
|
||||
{{ isLoading ? loadingText : text }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Loading Button Component
|
||||
* Professional Angular Resume Builder - Enterprise Button with Spinner
|
||||
*
|
||||
* @author David Valera Melendez <david@valera-melendez.de>
|
||||
* @created 2025-08-08
|
||||
* @location Made in Germany 🇩🇪
|
||||
*/
|
||||
|
||||
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { LoadingService } from '../../../services/loading.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-loading-button',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
templateUrl: './loading-button.component.html',
|
||||
styleUrls: ['./loading-button.component.css']
|
||||
})
|
||||
export class LoadingButtonComponent implements OnInit, OnDestroy {
|
||||
@Input() text: string = 'Submit';
|
||||
@Input() loadingText: string = 'Processing...';
|
||||
@Input() formId: string = 'default';
|
||||
@Input() icon: string = '';
|
||||
@Input() color: 'primary' | 'accent' | 'warn' = 'primary';
|
||||
@Input() disabled: boolean = false;
|
||||
@Input() size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@Input() spinnerSize: number = 20;
|
||||
@Input() spinnerColor: 'primary' | 'accent' | 'warn' = 'primary';
|
||||
|
||||
@Output() clicked = new EventEmitter<void>();
|
||||
|
||||
isLoading = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private loadingService: LoadingService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Subscribe to form-specific loading state
|
||||
this.loadingService.isFormLoading(this.formId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((loading: boolean) => {
|
||||
this.isLoading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get buttonClass(): string {
|
||||
const classes = [this.size];
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
onButtonClick(): void {
|
||||
if (!this.isLoading && !this.disabled) {
|
||||
this.clicked.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user