mirror of
https://github.com/JohanLeroy/dialogue-lib.git
synced 2026-01-27 09:47:31 +00:00
first commit
This commit is contained in:
10
ng-package.json
Normal file
10
ng-package.json
Normal 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
20
package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
23
src/components/alert-icon.component.html
Normal file
23
src/components/alert-icon.component.html
Normal 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>
|
||||
}
|
||||
}
|
||||
12
src/components/alert-icon.component.ts
Normal file
12
src/components/alert-icon.component.ts
Normal 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;
|
||||
}
|
||||
34
src/components/alert.component.html
Normal file
34
src/components/alert.component.html
Normal 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>
|
||||
}
|
||||
76
src/components/alert.component.ts
Normal file
76
src/components/alert.component.ts
Normal 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
10
src/models/alert.ts
Normal 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
4
src/public-api.ts
Normal 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';
|
||||
39
src/services/alert.service.ts
Normal file
39
src/services/alert.service.ts
Normal 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
9
tsconfig.base.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"importHelpers": true
|
||||
}
|
||||
}
|
||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
tsconfig.lib.json
Normal file
15
tsconfig.lib.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user