commit 5da61af76ed10312733d4ba3e8bbb5496aebe8e5 Author: JLEROY Date: Tue Jun 24 10:55:12 2025 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ng-package.json b/ng-package.json new file mode 100644 index 0000000..beb4891 --- /dev/null +++ b/ng-package.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/ng-packagr/ng-package.schema.json", + "dest": "dist", + "lib": { + "entryFile": "src/public-api.ts" + }, + "allowedNonPeerDependencies": [ + "@angular/animations" + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..607c8ef --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/src/components/alert-icon.component.html b/src/components/alert-icon.component.html new file mode 100644 index 0000000..d2cdb21 --- /dev/null +++ b/src/components/alert-icon.component.html @@ -0,0 +1,23 @@ +@switch (type) { + @case ('error') { + + + + } + @case ('question') { + + + + } + @default { + + + + } +} diff --git a/src/components/alert-icon.component.ts b/src/components/alert-icon.component.ts new file mode 100644 index 0000000..43ac502 --- /dev/null +++ b/src/components/alert-icon.component.ts @@ -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; +} diff --git a/src/components/alert.component.html b/src/components/alert.component.html new file mode 100644 index 0000000..fb7d300 --- /dev/null +++ b/src/components/alert.component.html @@ -0,0 +1,34 @@ +@if (alertData()) { +
+
+
+ +

{{ alertData()?.title }}

+

{{ alertData()?.text }}

+
+
+ @if (alertData()?.type === 'question') { + + } + +
+
+
+} diff --git a/src/components/alert.component.ts b/src/components/alert.component.ts new file mode 100644 index 0000000..5373404 --- /dev/null +++ b/src/components/alert.component.ts @@ -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(null); + alertData = computed(() => this.current()); + + private autoCloseTimeout: any; + private countdownInterval: any; + countdown = signal(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); + } + +} diff --git a/src/models/alert.ts b/src/models/alert.ts new file mode 100644 index 0000000..22aff63 --- /dev/null +++ b/src/models/alert.ts @@ -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; +} diff --git a/src/public-api.ts b/src/public-api.ts new file mode 100644 index 0000000..9b19b82 --- /dev/null +++ b/src/public-api.ts @@ -0,0 +1,4 @@ +export * from './components/alert.component'; +export * from './components/alert-icon.component'; +export * from './services/alert.service'; +export * from './models/alert'; \ No newline at end of file diff --git a/src/services/alert.service.ts b/src/services/alert.service.ts new file mode 100644 index 0000000..3daaaac --- /dev/null +++ b/src/services/alert.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; +import { Alert } from '../models/alert'; + +type AlertParams = Partial>; + +@Injectable({ providedIn: 'root' }) +export class AlertService { + + private alertSubject = new Subject(); + + 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 { + return new Promise((resolve) => { + this.alertSubject.next({ + type, + title, + text, + confirmText, + cancelText, + timeout, + backdropOpacity, + resolve, + }); + }); + } + +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..93d6cfe --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "node", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "importHelpers": true + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..94fa839 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} \ No newline at end of file diff --git a/tsconfig.lib.json b/tsconfig.lib.json new file mode 100644 index 0000000..a0a82f6 --- /dev/null +++ b/tsconfig.lib.json @@ -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"] +} \ No newline at end of file