@tsiky/components-r19 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +35 -33
- package/package.json +1 -1
- package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +123 -89
- package/src/components/Charts/bar-chart/BarChart.tsx +167 -132
- package/src/components/Charts/mixed-chart/MixedChart.tsx +65 -9
- package/src/components/Charts/sankey-chart/SankeyChart.tsx +183 -155
- package/src/components/Confirmationpopup/ConfirmationPopup.module.css +88 -0
- package/src/components/Confirmationpopup/ConfirmationPopup.stories.tsx +94 -0
- package/src/components/Confirmationpopup/ConfirmationPopup.tsx +47 -0
- package/src/components/Confirmationpopup/index.ts +6 -0
- package/src/components/Confirmationpopup/useConfirmationPopup.ts +48 -0
- package/src/components/DayStatCard/DayStatCard.tsx +96 -69
- package/src/components/DynamicTable/AdvancedFilters.tsx +196 -196
- package/src/components/DynamicTable/ColumnSorter.tsx +185 -185
- package/src/components/DynamicTable/Pagination.tsx +115 -115
- package/src/components/DynamicTable/TableauDynamique.module.css +1287 -1287
- package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -69
- package/src/components/EntryControl/EntryControl.tsx +117 -117
- package/src/components/Grid/Grid.tsx +5 -0
- package/src/components/Header/Header.tsx +4 -2
- package/src/components/Header/header.css +61 -31
- package/src/components/MetricsPanel/MetricsPanel.module.css +688 -636
- package/src/components/MetricsPanel/MetricsPanel.tsx +220 -282
- package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +148 -125
- package/src/components/NavBar/NavBar.tsx +1 -1
- package/src/components/SelectFilter/SelectFilter.module.css +249 -0
- package/src/components/SelectFilter/SelectFilter.stories.tsx +321 -0
- package/src/components/SelectFilter/SelectFilter.tsx +219 -0
- package/src/components/SelectFilter/index.ts +2 -0
- package/src/components/SelectFilter/types.ts +19 -0
- package/src/components/TranslationKey/TranslationKey.tsx +265 -245
- package/src/components/TrendList/TrendList.tsx +72 -45
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import Chart from 'react-apexcharts';
|
|
3
3
|
import type { ApexOptions } from 'apexcharts';
|
|
4
|
+
import { Move } from 'lucide-react';
|
|
4
5
|
|
|
5
6
|
/* ---------- Types ---------- */
|
|
6
7
|
export interface BandChartData {
|
|
@@ -59,6 +60,9 @@ interface Props {
|
|
|
59
60
|
config?: BandChartConfig;
|
|
60
61
|
height?: number;
|
|
61
62
|
useOnGoing?: boolean;
|
|
63
|
+
draggable?: boolean;
|
|
64
|
+
tooltipEnabled?: boolean;
|
|
65
|
+
// zoomEnabled removed as separate prop — zoom/pan follow tooltipEnabled
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
/* ---------- Local series item types (compat Apex) ---------- */
|
|
@@ -140,7 +144,24 @@ const MixedChart: React.FC<Props> = ({
|
|
|
140
144
|
config = defaultConfig,
|
|
141
145
|
height = 350,
|
|
142
146
|
useOnGoing,
|
|
147
|
+
draggable = false,
|
|
148
|
+
tooltipEnabled = true,
|
|
143
149
|
}) => {
|
|
150
|
+
const [renderKey, setRenderKey] = useState(0);
|
|
151
|
+
const [loading, setLoading] = useState(false);
|
|
152
|
+
|
|
153
|
+
// Lorsque draggable passe de true → false, on force remount
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!draggable) {
|
|
156
|
+
setLoading(true);
|
|
157
|
+
const timeout = setTimeout(() => {
|
|
158
|
+
setRenderKey((k) => k + 1); // nouvelle key → remount Chart
|
|
159
|
+
setLoading(false);
|
|
160
|
+
}, 10); // très court délai pour permettre le placeholder
|
|
161
|
+
return () => clearTimeout(timeout);
|
|
162
|
+
}
|
|
163
|
+
}, [draggable]);
|
|
164
|
+
|
|
144
165
|
// merge defaults and provided config
|
|
145
166
|
const merged = { ...defaultConfig, ...config };
|
|
146
167
|
// minimal override: if caller provided useOnGoing boolean, it forces merged.showOnGoing
|
|
@@ -480,9 +501,26 @@ const MixedChart: React.FC<Props> = ({
|
|
|
480
501
|
chart: {
|
|
481
502
|
foreColor: 'var(--color-text)',
|
|
482
503
|
type: 'line',
|
|
483
|
-
toolbar: {
|
|
504
|
+
toolbar: {
|
|
505
|
+
show: false,
|
|
506
|
+
offsetY: -20,
|
|
507
|
+
autoSelected: 'pan',
|
|
508
|
+
tools: {
|
|
509
|
+
download: false,
|
|
510
|
+
selection: true,
|
|
511
|
+
zoom: true,
|
|
512
|
+
zoomin: true,
|
|
513
|
+
zoomout: true,
|
|
514
|
+
pan: true,
|
|
515
|
+
reset: true,
|
|
516
|
+
},
|
|
517
|
+
},
|
|
484
518
|
width: '100%',
|
|
485
|
-
zoom: {
|
|
519
|
+
zoom: {
|
|
520
|
+
enabled: tooltipEnabled,
|
|
521
|
+
type: 'x',
|
|
522
|
+
autoScaleYaxis: true,
|
|
523
|
+
},
|
|
486
524
|
animations: {
|
|
487
525
|
enabled: true,
|
|
488
526
|
animateGradually: { enabled: true, delay: 80 },
|
|
@@ -525,6 +563,7 @@ const MixedChart: React.FC<Props> = ({
|
|
|
525
563
|
// CUSTOM TOOLTIP: show only defined values, keep Real and OnGoing separate,
|
|
526
564
|
// skip outlier points flagged _outside, skip column placeholder zeros
|
|
527
565
|
tooltip: {
|
|
566
|
+
enabled: tooltipEnabled,
|
|
528
567
|
shared: true,
|
|
529
568
|
intersect: false,
|
|
530
569
|
followCursor: true,
|
|
@@ -613,12 +652,29 @@ const MixedChart: React.FC<Props> = ({
|
|
|
613
652
|
};
|
|
614
653
|
|
|
615
654
|
return (
|
|
616
|
-
<
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
655
|
+
<div style={{ position: 'relative', width: '100%', height }}>
|
|
656
|
+
{draggable && !loading ? (
|
|
657
|
+
<div
|
|
658
|
+
style={{
|
|
659
|
+
width: '100%',
|
|
660
|
+
height: '100%',
|
|
661
|
+
display: 'flex',
|
|
662
|
+
alignItems: 'center',
|
|
663
|
+
justifyContent: 'center',
|
|
664
|
+
}}
|
|
665
|
+
>
|
|
666
|
+
<Move size={48} strokeWidth={1.5} style={{ opacity: 0.8 }} />
|
|
667
|
+
</div>
|
|
668
|
+
) : (
|
|
669
|
+
<Chart
|
|
670
|
+
key={renderKey} // force remount si besoin
|
|
671
|
+
options={options}
|
|
672
|
+
series={series as unknown as ApexOptions['series']}
|
|
673
|
+
type="line"
|
|
674
|
+
height={height}
|
|
675
|
+
/>
|
|
676
|
+
)}
|
|
677
|
+
</div>
|
|
622
678
|
);
|
|
623
679
|
};
|
|
624
680
|
|
|
@@ -1,155 +1,183 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export type SankeyLink = { source: string; target: string; value: number; color?: string };
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
links?: SankeyLink[];
|
|
8
|
-
height?: number;
|
|
9
|
-
nodeWidth?: number;
|
|
10
|
-
enableToolbar?: boolean;
|
|
11
|
-
annotatePercent?: boolean;
|
|
12
|
-
maxLeftFraction?: number;
|
|
13
|
-
className?: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{ source: 'non-seniors', target: '
|
|
44
|
-
{ source: 'non-seniors', target: '
|
|
45
|
-
{ source: 'non-seniors', target: '
|
|
46
|
-
{ source: 'seniors', target: '
|
|
47
|
-
{ source: 'seniors', target: '
|
|
48
|
-
{ source: 'seniors', target: '
|
|
49
|
-
{ source: 'seniors', target: '
|
|
50
|
-
{ source: 'seniors', target: '
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
1
|
+
import { Move } from 'lucide-react';
|
|
2
|
+
import React, { useEffect, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
export type SankeyLink = { source: string; target: string; value: number; color?: string };
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
links?: SankeyLink[];
|
|
8
|
+
height?: number;
|
|
9
|
+
nodeWidth?: number;
|
|
10
|
+
enableToolbar?: boolean;
|
|
11
|
+
annotatePercent?: boolean;
|
|
12
|
+
maxLeftFraction?: number;
|
|
13
|
+
className?: string;
|
|
14
|
+
draggable?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SankeyChart: React.FC<Props> = ({
|
|
18
|
+
links,
|
|
19
|
+
height = 480,
|
|
20
|
+
nodeWidth = 5,
|
|
21
|
+
enableToolbar = false,
|
|
22
|
+
annotatePercent = true,
|
|
23
|
+
maxLeftFraction = 0.45,
|
|
24
|
+
className,
|
|
25
|
+
draggable = false,
|
|
26
|
+
}) => {
|
|
27
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
let sInstance: { render: (d: unknown) => void; destroy?: () => void } | null = null;
|
|
31
|
+
let mounted = true;
|
|
32
|
+
|
|
33
|
+
(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const mod = await import('apexsankey');
|
|
36
|
+
const ApexSankey = mod?.default as unknown;
|
|
37
|
+
|
|
38
|
+
if (!mounted) return;
|
|
39
|
+
const mountEl = containerRef.current;
|
|
40
|
+
if (!mountEl) return;
|
|
41
|
+
|
|
42
|
+
const defaultLinks: SankeyLink[] = [
|
|
43
|
+
{ source: 'non-seniors', target: 'retour', value: 73.9 },
|
|
44
|
+
{ source: 'non-seniors', target: 'hospitalisations', value: 23.9 },
|
|
45
|
+
{ source: 'non-seniors', target: 'transfert', value: 2.1 },
|
|
46
|
+
{ source: 'non-seniors', target: 'maison_medicale_de_garde', value: 0.1 },
|
|
47
|
+
{ source: 'non-seniors', target: 'deces', value: 0.1 },
|
|
48
|
+
{ source: 'seniors', target: 'retour', value: 75.6 },
|
|
49
|
+
{ source: 'seniors', target: 'hospitalisations', value: 20.4 },
|
|
50
|
+
{ source: 'seniors', target: 'transfert', value: 3.1 },
|
|
51
|
+
{ source: 'seniors', target: 'maison_medicale_de_garde', value: 0.1 },
|
|
52
|
+
{ source: 'seniors', target: 'deces', value: 0.8 },
|
|
53
|
+
];
|
|
54
|
+
const usedLinks = links && links.length ? links : defaultLinks;
|
|
55
|
+
|
|
56
|
+
const nodeSet = new Set<string>();
|
|
57
|
+
usedLinks.forEach((l) => {
|
|
58
|
+
nodeSet.add(l.source);
|
|
59
|
+
nodeSet.add(l.target);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const orderedNodes = [
|
|
63
|
+
'non-seniors',
|
|
64
|
+
'seniors',
|
|
65
|
+
'retour',
|
|
66
|
+
'hospitalisations',
|
|
67
|
+
'transfert',
|
|
68
|
+
'maison_medicale_de_garde',
|
|
69
|
+
'deces',
|
|
70
|
+
].filter((id) => nodeSet.has(id));
|
|
71
|
+
for (const id of nodeSet) if (!orderedNodes.includes(id)) orderedNodes.push(id);
|
|
72
|
+
|
|
73
|
+
const totalsBySource: Record<string, number> = {};
|
|
74
|
+
const totalsByTarget: Record<string, number> = {};
|
|
75
|
+
let overall = 0;
|
|
76
|
+
usedLinks.forEach((l) => {
|
|
77
|
+
totalsBySource[l.source] = (totalsBySource[l.source] || 0) + (l.value ?? 0);
|
|
78
|
+
totalsByTarget[l.target] = (totalsByTarget[l.target] || 0) + (l.value ?? 0);
|
|
79
|
+
overall += l.value ?? 0;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const nodes = orderedNodes.map((id) => {
|
|
83
|
+
let title = id;
|
|
84
|
+
if (annotatePercent) {
|
|
85
|
+
const srcVal = totalsBySource[id];
|
|
86
|
+
if (typeof srcVal === 'number' && overall > 0) {
|
|
87
|
+
const pct = Math.round((srcVal / overall) * 1000) / 10;
|
|
88
|
+
title = `${id} (${pct}%)`;
|
|
89
|
+
} else {
|
|
90
|
+
const t = totalsByTarget[id];
|
|
91
|
+
if (typeof t === 'number' && overall > 0) {
|
|
92
|
+
const pct = Math.round((t / overall) * 1000) / 10;
|
|
93
|
+
title = `${id} (${pct}%)`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { id, title };
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const edges = usedLinks.map((l) => ({
|
|
101
|
+
source: l.source,
|
|
102
|
+
target: l.target,
|
|
103
|
+
value: l.value,
|
|
104
|
+
}));
|
|
105
|
+
const data = { nodes, edges };
|
|
106
|
+
|
|
107
|
+
const parent = mountEl?.parentElement;
|
|
108
|
+
const containerWidth = parent ? parent.clientWidth || 420 : mountEl?.clientWidth || 420;
|
|
109
|
+
|
|
110
|
+
const graphOptions = {
|
|
111
|
+
nodeWidth,
|
|
112
|
+
fontFamily:
|
|
113
|
+
"Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial",
|
|
114
|
+
fontWeight: '600',
|
|
115
|
+
fontColor: 'var(--color-text)',
|
|
116
|
+
height,
|
|
117
|
+
enableToolbar,
|
|
118
|
+
viewPortWidth: containerWidth,
|
|
119
|
+
viewPortHeight: height,
|
|
120
|
+
width: containerWidth,
|
|
121
|
+
nodeAlignment: 'top',
|
|
122
|
+
colors: {
|
|
123
|
+
text: 'var(--color-text)',
|
|
124
|
+
background: '#fff',
|
|
125
|
+
},
|
|
126
|
+
linkOpacity: 0.6,
|
|
127
|
+
canvasStyle: {},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// constructor type
|
|
131
|
+
const SankeyCtor = ApexSankey as unknown as new (
|
|
132
|
+
el: Element,
|
|
133
|
+
opts: unknown
|
|
134
|
+
) => { render: (d: unknown) => void; destroy?: () => void };
|
|
135
|
+
sInstance = new SankeyCtor(mountEl as Element, graphOptions as unknown);
|
|
136
|
+
sInstance.render(data as unknown);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error('Erreur lors du rendu ApexSankey :', err);
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
|
|
142
|
+
return () => {
|
|
143
|
+
mounted = false;
|
|
144
|
+
try {
|
|
145
|
+
if (sInstance && typeof sInstance.destroy === 'function') sInstance.destroy();
|
|
146
|
+
} catch {
|
|
147
|
+
/* noop */
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}, [links, height, nodeWidth, enableToolbar, annotatePercent, maxLeftFraction]);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div style={{ position: 'relative', height }}>
|
|
154
|
+
{/* Chart container, masqué si draggable */}
|
|
155
|
+
<div
|
|
156
|
+
ref={containerRef}
|
|
157
|
+
className={className}
|
|
158
|
+
style={{ height, overflow: 'hidden', opacity: draggable ? 0 : 1 }}
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
{/* Overlay Move */}
|
|
162
|
+
{draggable && (
|
|
163
|
+
<div
|
|
164
|
+
style={{
|
|
165
|
+
position: 'absolute',
|
|
166
|
+
top: 0,
|
|
167
|
+
left: 0,
|
|
168
|
+
width: '100%',
|
|
169
|
+
height: '100%',
|
|
170
|
+
display: 'flex',
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
justifyContent: 'center',
|
|
173
|
+
pointerEvents: 'none',
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
<Move size={32} strokeWidth={1.5} style={{ opacity: 0.8 }} />
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default SankeyChart;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.overlay {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
right: 0;
|
|
6
|
+
bottom: 0;
|
|
7
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
align-items: center;
|
|
11
|
+
z-index: 1000;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.popup {
|
|
15
|
+
background: white;
|
|
16
|
+
border-radius: 8px;
|
|
17
|
+
min-width: 400px;
|
|
18
|
+
max-width: 500px;
|
|
19
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.header {
|
|
23
|
+
padding: 1rem;
|
|
24
|
+
border-bottom: 1px solid #e5e5e5;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.header h3 {
|
|
28
|
+
margin: 0;
|
|
29
|
+
font-size: 1.25rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.body {
|
|
33
|
+
padding: 1.5rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.body p {
|
|
37
|
+
margin: 0;
|
|
38
|
+
line-height: 1.5;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.footer {
|
|
42
|
+
padding: 1rem;
|
|
43
|
+
border-top: 1px solid #e5e5e5;
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: flex-end;
|
|
46
|
+
gap: 0.5rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.confirmButton,
|
|
50
|
+
.cancelButton {
|
|
51
|
+
padding: 0.5rem 1rem;
|
|
52
|
+
border: none;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
font-size: 0.875rem;
|
|
56
|
+
transition: all 0.2s;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.confirmButton {
|
|
60
|
+
background-color: #dc3545;
|
|
61
|
+
color: white;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.confirmButton:hover {
|
|
65
|
+
background-color: #c82333;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.cancelButton {
|
|
69
|
+
background-color: #6c757d;
|
|
70
|
+
color: white;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.cancelButton:hover {
|
|
74
|
+
background-color: #5a6268;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Types variants */
|
|
78
|
+
.warning .header {
|
|
79
|
+
border-left: 4px solid #ffc107;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.danger .header {
|
|
83
|
+
border-left: 4px solid #dc3545;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.info .header {
|
|
87
|
+
border-left: 4px solid #17a2b8;
|
|
88
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ConfirmationPopup } from './ConfirmationPopup';
|
|
3
|
+
import { useConfirmationPopup } from './useConfirmationPopup';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof ConfirmationPopup> = {
|
|
6
|
+
title: 'Components/ConfirmationPopup',
|
|
7
|
+
component: ConfirmationPopup,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
args: {
|
|
10
|
+
title: 'Supprimer cet élément ?',
|
|
11
|
+
message: 'Cette action est irréversible. Voulez-vous vraiment supprimer cet élément ?',
|
|
12
|
+
confirmText: 'Oui, supprimer',
|
|
13
|
+
cancelText: 'Annuler',
|
|
14
|
+
type: 'warning',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof ConfirmationPopup>;
|
|
20
|
+
|
|
21
|
+
export const Default: Story = {
|
|
22
|
+
args: {
|
|
23
|
+
isOpen: true,
|
|
24
|
+
onConfirm: () => console.log('Confirmé!'),
|
|
25
|
+
onCancel: () => console.log('Annulé!'),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const ControlledExample = () => {
|
|
30
|
+
const { isOpen, title, message, showConfirmation, hideConfirmation, handleConfirm } =
|
|
31
|
+
useConfirmationPopup();
|
|
32
|
+
|
|
33
|
+
const handleDelete = () => {
|
|
34
|
+
console.log('Element supprimé!');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
<button
|
|
40
|
+
onClick={() =>
|
|
41
|
+
showConfirmation(
|
|
42
|
+
'Supprimer cet élément ?',
|
|
43
|
+
'Cette action est irréversible.',
|
|
44
|
+
handleDelete
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
Supprimer un élément
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
<ConfirmationPopup
|
|
52
|
+
isOpen={isOpen}
|
|
53
|
+
title={title}
|
|
54
|
+
message={message}
|
|
55
|
+
onConfirm={handleConfirm}
|
|
56
|
+
onCancel={hideConfirmation}
|
|
57
|
+
confirmText='Confirmer'
|
|
58
|
+
cancelText='Annuler'
|
|
59
|
+
type='danger'
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const DifferentTypes: Story = {
|
|
66
|
+
render: () => (
|
|
67
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
|
68
|
+
<ConfirmationPopup
|
|
69
|
+
isOpen={true}
|
|
70
|
+
title='Information'
|
|
71
|
+
message='Ceci est une information'
|
|
72
|
+
onConfirm={() => {}}
|
|
73
|
+
onCancel={() => {}}
|
|
74
|
+
type='info'
|
|
75
|
+
/>
|
|
76
|
+
<ConfirmationPopup
|
|
77
|
+
isOpen={true}
|
|
78
|
+
title='Attention'
|
|
79
|
+
message='Ceci est un avertissement'
|
|
80
|
+
onConfirm={() => {}}
|
|
81
|
+
onCancel={() => {}}
|
|
82
|
+
type='warning'
|
|
83
|
+
/>
|
|
84
|
+
<ConfirmationPopup
|
|
85
|
+
isOpen={true}
|
|
86
|
+
title='Danger'
|
|
87
|
+
message='Ceci est une action dangereuse'
|
|
88
|
+
onConfirm={() => {}}
|
|
89
|
+
onCancel={() => {}}
|
|
90
|
+
type='danger'
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
),
|
|
94
|
+
};
|