@tsiky/components-r19 1.0.0 → 1.2.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.
Files changed (61) hide show
  1. package/index.ts +35 -33
  2. package/package.json +1 -1
  3. package/src/components/AnnouncementPanel/FlexRowContainer.css +17 -17
  4. package/src/components/AnnouncementPanel/FlexRowContainer.stories.tsx +329 -329
  5. package/src/components/AnnouncementPanel/FlexRowContainer.tsx +24 -24
  6. package/src/components/AnnouncementPanel/ListBox/CounterListBox.css +56 -56
  7. package/src/components/AnnouncementPanel/ListBox/CounterListBox.stories.tsx +292 -292
  8. package/src/components/AnnouncementPanel/ListBox/CounterListBox.tsx +106 -106
  9. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.css +57 -57
  10. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.stories.tsx +189 -189
  11. package/src/components/AnnouncementPanel/ListBox/SimpleListBox.tsx +138 -138
  12. package/src/components/AnnouncementPanel/ListBox/TrendListBox.css +61 -61
  13. package/src/components/AnnouncementPanel/ListBox/TrendListBox.stories.tsx +257 -257
  14. package/src/components/AnnouncementPanel/ListBox/TrendListBox.tsx +90 -90
  15. package/src/components/AnnouncementPanel/ListBox/index.ts +3 -3
  16. package/src/components/AnnouncementPanel/ListContentContainer.css +23 -23
  17. package/src/components/AnnouncementPanel/ListContentContainer.stories.tsx +212 -212
  18. package/src/components/AnnouncementPanel/ListContentContainer.tsx +33 -33
  19. package/src/components/AnnouncementPanel/index.ts +3 -3
  20. package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +129 -89
  21. package/src/components/Charts/bar-chart/BarChart.tsx +171 -132
  22. package/src/components/Charts/boxplot-chart/BoxPlotChart.tsx +114 -114
  23. package/src/components/Charts/mixed-chart/MixedChart.tsx +65 -9
  24. package/src/components/Charts/sankey-adaptation/sankey.tsx +70 -70
  25. package/src/components/Charts/sankey-chart/SankeyChart.tsx +183 -155
  26. package/src/components/Confirmationpopup/ConfirmationPopup.module.css +88 -0
  27. package/src/components/Confirmationpopup/ConfirmationPopup.stories.tsx +94 -0
  28. package/src/components/Confirmationpopup/ConfirmationPopup.tsx +47 -0
  29. package/src/components/Confirmationpopup/index.ts +6 -0
  30. package/src/components/Confirmationpopup/useConfirmationPopup.ts +48 -0
  31. package/src/components/DayStatCard/DayStatCard.tsx +96 -69
  32. package/src/components/DraggableSwitcher/DraggableSwitcherButton.tsx +58 -58
  33. package/src/components/DraggableSwitcher/context/useDraggableSwitcher.tsx +45 -45
  34. package/src/components/DraggableSwitcher/index.ts +2 -2
  35. package/src/components/DynamicInput/input/SelectInput.tsx +75 -75
  36. package/src/components/DynamicInput/input/assets/SelectInput.module.css +95 -95
  37. package/src/components/DynamicTable/TableCell.tsx +38 -30
  38. package/src/components/DynamicTable/TableHeader.tsx +39 -34
  39. package/src/components/DynamicTable/TableauDynamique.module.css +1333 -1287
  40. package/src/components/DynamicTable/TableauDynamique.tsx +154 -154
  41. package/src/components/DynamicTable/tools/tableTypes.ts +63 -63
  42. package/src/components/Grid/Grid.tsx +5 -0
  43. package/src/components/Grid/grid.css +285 -285
  44. package/src/components/Header/Header.tsx +4 -2
  45. package/src/components/Header/header.css +61 -31
  46. package/src/components/MetricsPanel/MetricsPanel.module.css +688 -636
  47. package/src/components/MetricsPanel/MetricsPanel.tsx +220 -282
  48. package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +148 -125
  49. package/src/components/NavBar/NavBar.tsx +1 -1
  50. package/src/components/NavItem/NavItem.tsx +58 -58
  51. package/src/components/PeriodRange/PeriodRange.module.css +158 -158
  52. package/src/components/PeriodRange/PeriodRange.tsx +130 -130
  53. package/src/components/SearchBar/SearchBar.css +40 -40
  54. package/src/components/SelectFilter/SelectFilter.module.css +249 -0
  55. package/src/components/SelectFilter/SelectFilter.stories.tsx +321 -0
  56. package/src/components/SelectFilter/SelectFilter.tsx +219 -0
  57. package/src/components/SelectFilter/index.ts +2 -0
  58. package/src/components/SelectFilter/types.ts +19 -0
  59. package/src/components/TranslationKey/TranslationKey.css +272 -272
  60. package/src/components/TranslationKey/TranslationKey.tsx +266 -245
  61. package/src/components/TrendList/TrendList.tsx +72 -45
@@ -1,155 +1,183 @@
1
- import React, { useEffect, useRef } from 'react';
2
- import { color } from 'storybook/internal/theming';
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
- const SankeyChart: React.FC<Props> = ({
17
- links,
18
- height = 480,
19
- nodeWidth = 5,
20
- enableToolbar = false,
21
- annotatePercent = true,
22
- maxLeftFraction = 0.45,
23
- className,
24
- }) => {
25
- const containerRef = useRef<HTMLDivElement | null>(null);
26
-
27
- useEffect(() => {
28
- let sInstance: { render: (d: unknown) => void; destroy?: () => void } | null = null;
29
- let mounted = true;
30
-
31
- (async () => {
32
- try {
33
- const mod = await import('apexsankey');
34
- const ApexSankey = mod?.default as unknown;
35
-
36
- if (!mounted) return;
37
- const mountEl = containerRef.current;
38
- if (!mountEl) return;
39
-
40
- const defaultLinks: SankeyLink[] = [
41
- { source: 'non-seniors', target: 'retour', value: 73.9 },
42
- { source: 'non-seniors', target: 'hospitalisations', value: 23.9 },
43
- { source: 'non-seniors', target: 'transfert', value: 2.1 },
44
- { source: 'non-seniors', target: 'maison_medicale_de_garde', value: 0.1 },
45
- { source: 'non-seniors', target: 'deces', value: 0.1 },
46
- { source: 'seniors', target: 'retour', value: 75.6 },
47
- { source: 'seniors', target: 'hospitalisations', value: 20.4 },
48
- { source: 'seniors', target: 'transfert', value: 3.1 },
49
- { source: 'seniors', target: 'maison_medicale_de_garde', value: 0.1 },
50
- { source: 'seniors', target: 'deces', value: 0.8 },
51
- ];
52
- const usedLinks = links && links.length ? links : defaultLinks;
53
-
54
- const nodeSet = new Set<string>();
55
- usedLinks.forEach((l) => {
56
- nodeSet.add(l.source);
57
- nodeSet.add(l.target);
58
- });
59
-
60
- const orderedNodes = [
61
- 'non-seniors',
62
- 'seniors',
63
- 'retour',
64
- 'hospitalisations',
65
- 'transfert',
66
- 'maison_medicale_de_garde',
67
- 'deces',
68
- ].filter((id) => nodeSet.has(id));
69
- for (const id of nodeSet) if (!orderedNodes.includes(id)) orderedNodes.push(id);
70
-
71
- const totalsBySource: Record<string, number> = {};
72
- const totalsByTarget: Record<string, number> = {};
73
- let overall = 0;
74
- usedLinks.forEach((l) => {
75
- totalsBySource[l.source] = (totalsBySource[l.source] || 0) + (l.value ?? 0);
76
- totalsByTarget[l.target] = (totalsByTarget[l.target] || 0) + (l.value ?? 0);
77
- overall += l.value ?? 0;
78
- });
79
-
80
- const nodes = orderedNodes.map((id) => {
81
- let title = id;
82
- if (annotatePercent) {
83
- const srcVal = totalsBySource[id];
84
- if (typeof srcVal === 'number' && overall > 0) {
85
- const pct = Math.round((srcVal / overall) * 1000) / 10;
86
- title = `${id} (${pct}%)`;
87
- } else {
88
- const t = totalsByTarget[id];
89
- if (typeof t === 'number' && overall > 0) {
90
- const pct = Math.round((t / overall) * 1000) / 10;
91
- title = `${id} (${pct}%)`;
92
- }
93
- }
94
- }
95
- return { id, title };
96
- });
97
-
98
- const edges = usedLinks.map((l) => ({
99
- source: l.source,
100
- target: l.target,
101
- value: l.value,
102
- }));
103
- const data = { nodes, edges };
104
-
105
- const parent = mountEl?.parentElement;
106
- const containerWidth = parent ? parent.clientWidth || 420 : mountEl?.clientWidth || 420;
107
-
108
- const graphOptions = {
109
- nodeWidth,
110
- fontFamily:
111
- "Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial",
112
- fontWeight: '600',
113
- fontColor: 'var(--color-text)',
114
- height,
115
- enableToolbar,
116
- viewPortWidth: containerWidth,
117
- viewPortHeight: height,
118
- width: containerWidth,
119
- nodeAlignment: 'top',
120
- colors: {
121
- text: 'var(--color-text)',
122
- background: '#fff',
123
- },
124
- linkOpacity: 0.6,
125
- canvasStyle: {},
126
- };
127
-
128
- // constructor type
129
- const SankeyCtor = ApexSankey as unknown as new (
130
- el: Element,
131
- opts: unknown
132
- ) => { render: (d: unknown) => void; destroy?: () => void };
133
- sInstance = new SankeyCtor(mountEl as Element, graphOptions as unknown);
134
- sInstance.render(data as unknown);
135
- } catch (err) {
136
- console.error('Erreur lors du rendu ApexSankey :', err);
137
- }
138
- })();
139
-
140
- return () => {
141
- mounted = false;
142
- try {
143
- if (sInstance && typeof sInstance.destroy === 'function') sInstance.destroy();
144
- } catch {
145
- /* noop */
146
- }
147
- };
148
- }, [links, height, nodeWidth, enableToolbar, annotatePercent, maxLeftFraction]);
149
-
150
- return (
151
- <div ref={containerRef} className={className} style={{ height: height, overflow: 'hidden' }} />
152
- );
153
- };
154
-
155
- export default SankeyChart;
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
+ };
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import styles from './ConfirmationPopup.module.css';
3
+
4
+ interface ConfirmationPopupProps {
5
+ isOpen: boolean;
6
+ title: string;
7
+ message: string;
8
+ onConfirm: () => void;
9
+ onCancel: () => void;
10
+ confirmText?: string;
11
+ cancelText?: string;
12
+ type?: 'warning' | 'info' | 'danger';
13
+ }
14
+
15
+ export const ConfirmationPopup: React.FC<ConfirmationPopupProps> = ({
16
+ isOpen,
17
+ title,
18
+ message,
19
+ onConfirm,
20
+ onCancel,
21
+ confirmText = 'Confirmer',
22
+ cancelText = 'Annuler',
23
+ type = 'warning',
24
+ }) => {
25
+ if (!isOpen) return null;
26
+
27
+ return (
28
+ <div className={styles.overlay}>
29
+ <div className={`${styles.popup} ${styles[type]}`}>
30
+ <div className={styles.header}>
31
+ <h3>{title}</h3>
32
+ </div>
33
+ <div className={styles.body}>
34
+ <p>{message}</p>
35
+ </div>
36
+ <div className={styles.footer}>
37
+ <button className={styles.cancelButton} onClick={onCancel}>
38
+ {cancelText}
39
+ </button>
40
+ <button className={styles.confirmButton} onClick={onConfirm}>
41
+ {confirmText}
42
+ </button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ );
47
+ };
@@ -0,0 +1,6 @@
1
+ import { ConfirmationPopup } from './ConfirmationPopup';
2
+ import { useConfirmationPopup } from './useConfirmationPopup';
3
+
4
+ export { ConfirmationPopup, useConfirmationPopup };
5
+
6
+ export default { ConfirmationPopup, useConfirmationPopup };
@@ -0,0 +1,48 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ interface ConfirmationState {
4
+ isOpen: boolean;
5
+ title: string;
6
+ message: string;
7
+ onConfirm: (() => void) | null;
8
+ }
9
+
10
+ export const useConfirmationPopup = () => {
11
+ const [confirmationState, setConfirmationState] = useState<ConfirmationState>({
12
+ isOpen: false,
13
+ title: '',
14
+ message: '',
15
+ onConfirm: null,
16
+ });
17
+
18
+ const showConfirmation = useCallback((title: string, message: string, onConfirm: () => void) => {
19
+ setConfirmationState({
20
+ isOpen: true,
21
+ title,
22
+ message,
23
+ onConfirm,
24
+ });
25
+ }, []);
26
+
27
+ const hideConfirmation = useCallback(() => {
28
+ setConfirmationState((prev) => ({
29
+ ...prev,
30
+ isOpen: false,
31
+ onConfirm: null,
32
+ }));
33
+ }, []);
34
+
35
+ const handleConfirm = useCallback(() => {
36
+ confirmationState.onConfirm?.();
37
+ hideConfirmation();
38
+ }, [confirmationState.onConfirm, hideConfirmation]);
39
+
40
+ return {
41
+ isOpen: confirmationState.isOpen,
42
+ title: confirmationState.title,
43
+ message: confirmationState.message,
44
+ showConfirmation,
45
+ hideConfirmation,
46
+ handleConfirm,
47
+ };
48
+ };