first commit

This commit is contained in:
JLEROY
2025-06-24 10:55:12 +02:00
commit 5da61af76e
13 changed files with 263 additions and 0 deletions

0
README.md Normal file
View File

10
ng-package.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"dest": "dist",
"lib": {
"entryFile": "src/public-api.ts"
},
"allowedNonPeerDependencies": [
"@angular/animations"
]
}

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "dialogue-lib",
"version": "1.0.0",
"description": "Système de dialogue Angular standalone avec alertes personnalisables",
"author": "Johan Leroy",
"license": "MIT",
"devDependencies": {
"@angular/compiler-cli": "^20.0.0",
"ng-packagr": "next",
"typescript": "~5.8.0"
},
"peerDependencies": {
"@angular/core": "^20.0.0",
"@angular/common": "^20.0.0",
"@angular/animations": "^20.0.0"
},
"scripts": {
"build": "ng-packagr -p ng-package.json"
}
}

View File

@@ -0,0 +1,23 @@
@switch (type) {
@case ('error') {
<svg class="w-16 h-16 text-red-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm-1-4h2v2h-2v-2zm0-8h2v6h-2V6z"
clip-rule="evenodd" />
</svg>
}
@case ('question') {
<svg class="w-16 h-16 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M8.257 3.099c.764-1.36 2.722-1.36 3.486 0l5.451 9.713c.75 1.337-.213 3.038-1.743 3.038H4.549c-1.53 0-2.493-1.7-1.743-3.038l5.451-9.713zM11 13a1 1 0 10-2 0 1 1 0 002 0zm-1-2a1 1 0 01-1-1V7a1 1 0 112 0v3a1 1 0 01-1 1z"
clip-rule="evenodd" />
</svg>
}
@default {
<svg class="w-16 h-16 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.707a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
}
}

View File

@@ -0,0 +1,12 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-alert-icon',
standalone: true,
imports: [CommonModule],
templateUrl: './alert-icon.component.html',
})
export class AlertIconComponent {
@Input() type: 'success' | 'error' | 'question' | undefined;
}

View File

@@ -0,0 +1,34 @@
@if (alertData()) {
<div class="fixed inset-0 z-500 flex justify-center items-center"
[ngStyle]="{ 'background-color': 'rgba(0, 0, 0, ' + (alertData()?.backdropOpacity ?? 0.1) + ')' }"
@fade>
<div class="w-full max-w-lg p-5 bg-white rounded-xl shadow-lg relative text-center animate-scaleIn">
<div class="p-5 flex flex-col items-center justify-center">
<app-alert-icon [type]="(alertData()?.type ?? 'success')" />
<h2 class="text-xl font-bold py-4">{{ alertData()?.title }}</h2>
<p class="text-sm text-gray-500 px-8">{{ alertData()?.text }}</p>
</div>
<div class="p-3 mt-2 text-center space-x-4">
@if (alertData()?.type === 'question') {
<button class="bg-white px-5 py-2 text-sm border text-gray-600 rounded-full hover:bg-gray-100 cursor-pointer"
(click)="cancel()">
{{ alertData()?.cancelText || 'Annuler' }}
</button>
}
<button
class="px-5 py-2 text-sm rounded-full text-white shadow-sm cursor-pointer"
[ngClass]="{
'bg-green-500 hover:bg-green-600': alertData()?.type === 'success',
'bg-red-500 hover:bg-red-600': alertData()?.type === 'error',
'bg-yellow-500 hover:bg-yellow-600': alertData()?.type === 'question'
}"
(click)="confirm()">
{{ alertData()?.confirmText }}
@if (countdown() !== null) {
({{ countdown() }})
}
</button>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,76 @@
import { Component, computed, effect, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AlertService } from '../services/alert.service';
import { Alert } from '../models/alert';
import {animate, style, transition, trigger} from '@angular/animations';
import {AlertIconComponent} from "./alert-icon.component";
@Component({
selector: 'app-alert',
standalone: true,
imports: [CommonModule, AlertIconComponent],
templateUrl: './alert.component.html',
animations: [
trigger('fade', [
transition(':enter', [
style({ opacity: 0 }),
animate('200ms ease-out', style({ opacity: 1 })),
]),
transition(':leave', [
animate('200ms ease-in', style({ opacity: 0 })),
]),
]),
],
})
export class AlertComponent {
private alertService = inject(AlertService);
private current = signal<Alert | null>(null);
alertData = computed(() => this.current());
private autoCloseTimeout: any;
private countdownInterval: any;
countdown = signal<number | null>(null);
constructor() {
effect(() => {
this.alertService.alerts().subscribe((data) => {
clearTimeout(this.autoCloseTimeout);
clearInterval(this.countdownInterval);
this.countdown.set(null);
this.current.set(data);
if (typeof data.timeout === 'number' && data.timeout > 0) {
const seconds = Math.floor(data.timeout / 1000);
this.countdown.set(seconds);
this.countdownInterval = setInterval(() => {
const current = this.countdown();
if (current !== null && current > 0) {
this.countdown.set(current - 1);
}
}, 1000);
this.autoCloseTimeout = setTimeout(() => {
this.cancel();
}, data.timeout);
}
});
});
}
confirm() {
this.current()?.resolve(true);
this.clear();
}
cancel() {
this.current()?.resolve(false);
this.clear();
}
private clear() {
this.current.set(null);
clearTimeout(this.autoCloseTimeout);
clearInterval(this.countdownInterval);
this.countdown.set(null);
}
}

10
src/models/alert.ts Normal file
View File

@@ -0,0 +1,10 @@
export interface Alert {
type: 'success' | 'error' | 'question';
title: string;
text: string;
confirmText: string;
cancelText?: string;
resolve: (confirmed: boolean) => void;
timeout?: number;
backdropOpacity?: number;
}

4
src/public-api.ts Normal file
View File

@@ -0,0 +1,4 @@
export * from './components/alert.component';
export * from './components/alert-icon.component';
export * from './services/alert.service';
export * from './models/alert';

View File

@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { Alert } from '../models/alert';
type AlertParams = Partial<Omit<Alert, 'resolve'>>;
@Injectable({ providedIn: 'root' })
export class AlertService {
private alertSubject = new Subject<Alert>();
alerts() {
return this.alertSubject.asObservable();
}
confirm({
type = 'question',
title = 'Êtes-vous sûr ?',
text = 'Cette action est irréversible.',
confirmText = 'Confirmer',
cancelText = 'Annuler',
timeout,
backdropOpacity = 0.1,
}: AlertParams): Promise<boolean> {
return new Promise((resolve) => {
this.alertSubject.next({
type,
title,
text,
confirmText,
cancelText,
timeout,
backdropOpacity,
resolve,
});
});
}
}

9
tsconfig.base.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"importHelpers": true
}
}

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true
},
"files": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

15
tsconfig.lib.json Normal file
View File

@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM"],
"declaration": true,
"declarationMap": true,
"skipLibCheck": true,
"strict": true,
"moduleResolution": "node",
"outDir": "dist/out-tsc",
"types": []
},
"exclude": ["node_modules", "dist"]
}