@madie/madie-design-system 1.2.57 → 1.2.59

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.
@@ -5,8 +5,8 @@ import InfoIcon from "@mui/icons-material/Info"; // info
5
5
  import CancelIcon from "@mui/icons-material/Cancel"; // error
6
6
  import CheckCircleIcon from "@mui/icons-material/CheckCircle"; //success
7
7
  import ContentCopyIcon from "@mui/icons-material/ContentCopy"; //copy
8
- import FullscreenExitRoundedIcon from '@mui/icons-material/FullscreenExitRounded';
9
- import WarningRoundedIcon from '@mui/icons-material/WarningRounded';
8
+ import FullscreenExitRoundedIcon from "@mui/icons-material/FullscreenExitRounded";
9
+ import WarningRoundedIcon from "@mui/icons-material/WarningRounded";
10
10
  import Tooltip from "@mui/material/Tooltip";
11
11
  import { IconButton } from "@mui/material";
12
12
  import ClearIcon from "@mui/icons-material/Clear";
@@ -16,336 +16,386 @@ import PropTypes from "prop-types";
16
16
 
17
17
  // Define icon mapping outside component to avoid recreation on each render
18
18
  const typeToIconMap = {
19
- warning: ErrorIcon,
20
- info: InfoIcon,
21
- error: CancelIcon,
22
- success: CheckCircleIcon,
19
+ warning: ErrorIcon,
20
+ info: InfoIcon,
21
+ error: CancelIcon,
22
+ success: CheckCircleIcon,
23
23
  };
24
24
 
25
25
  // Common button styles
26
26
  const buttonBaseSx = {
27
- marginLeft: "auto",
27
+ marginLeft: "auto",
28
28
  };
29
29
 
30
30
  const buttonWithDividerSx = {
31
- ...buttonBaseSx,
32
- "&:after": {
33
- content: `''`,
34
- position: "absolute",
35
- left: "0px",
36
- width: "1px",
37
- height: "40px",
38
- backgroundColor: "#B0B0B0",
39
- pointerEvents: "none",
40
- }
31
+ ...buttonBaseSx,
32
+ "&:after": {
33
+ content: `''`,
34
+ position: "absolute",
35
+ left: "0px",
36
+ width: "1px",
37
+ height: "40px",
38
+ backgroundColor: "#B0B0B0",
39
+ pointerEvents: "none",
40
+ },
41
41
  };
42
42
 
43
43
  // Extract alert content processing to a separate function
44
44
  const processContent = (content) => {
45
- // Counts errors/warnings in ul/li elements
46
- const countUlLiChildren = (element) => {
47
- const counts = { ul: 0, li: 0 };
48
-
49
- const traverse = (el) => {
50
- if (!el || typeof el !== 'object') return;
51
-
52
- if (el.type === 'ul' || el.type === 'li') {
53
- const children = el.props?.children;
54
- const childCount = Array.isArray(children) ? children.length : (children ? 1 : 0);
55
- counts[el.type] += childCount;
56
- }
57
-
58
- const children = el.props?.children;
59
- if (Array.isArray(children)) {
60
- children.forEach(traverse);
61
- } else if (children && typeof children === 'object') {
62
- traverse(children);
63
- }
45
+ // Counts errors/warnings in ul/li elements
46
+ const countUlLiChildren = (element) => {
47
+ const counts = { ul: 0, li: 0 };
48
+
49
+ const traverse = (el) => {
50
+ if (!el || typeof el !== "object") return;
51
+
52
+ if (el.type === "ul" || el.type === "li") {
53
+ const children = el.props?.children;
54
+ const childCount = Array.isArray(children)
55
+ ? children.length
56
+ : children
57
+ ? 1
58
+ : 0;
59
+ counts[el.type] += childCount;
60
+ }
61
+
62
+ const children = el.props?.children;
63
+ if (Array.isArray(children)) {
64
+ children.forEach(traverse);
65
+ } else if (children && typeof children === "object") {
66
+ traverse(children);
67
+ }
68
+ };
69
+
70
+ traverse(element);
71
+
72
+ return counts.li > counts.ul ? counts.li : counts.ul;
64
73
  };
65
-
66
- traverse(element);
67
-
68
- return counts.li > counts.ul ? counts.li : counts.ul;
69
- };
70
-
71
- // Converts content to text for copying - improved to avoid unwanted characters
72
- const buildCopyText = (contentNode, parentNode = true) => {
73
- if (!contentNode) return "";
74
-
75
- // Initialize result string
76
- let result = "";
77
-
78
- // Handle specific node types that need special formatting
79
- if (contentNode.type === "ul") {
80
- // Start ul with a newline
81
- result = "\n";
82
- }
83
-
84
- // Process children based on their type
85
- if (Array.isArray(contentNode.props?.children)) {
86
- contentNode.props.children.forEach((child, index) => {
87
- const childText = buildCopyText(child, false);
88
- // Only add non-empty text
89
- if (childText.trim()) {
90
- result += childText;
91
-
92
- // Add line breaks after certain elements
93
- if ((parentNode && index === 1) || contentNode.type === "ul") {
94
- result += "\n";
95
- }
74
+
75
+ // Converts content to text for copying - improved to avoid unwanted characters
76
+ const buildCopyText = (contentNode, parentNode = true) => {
77
+ if (!contentNode) return "";
78
+
79
+ // Initialize result string
80
+ let result = "";
81
+
82
+ // Handle specific node types that need special formatting
83
+ if (contentNode.type === "ul") {
84
+ // Start ul with a newline
85
+ result = "\n";
86
+ }
87
+
88
+ // Process children based on their type
89
+ if (Array.isArray(contentNode.props?.children)) {
90
+ contentNode.props.children.forEach((child, index) => {
91
+ const childText = buildCopyText(child, false);
92
+ // Only add non-empty text
93
+ if (childText.trim()) {
94
+ result += childText;
95
+
96
+ // Add line breaks after certain elements
97
+ if (
98
+ (parentNode && index === 1) ||
99
+ contentNode.type === "ul"
100
+ ) {
101
+ result += "\n";
102
+ }
103
+ }
104
+ });
105
+ } else if (typeof contentNode.props?.children === "object") {
106
+ result += buildCopyText(contentNode.props.children, false);
107
+ } else if (
108
+ typeof contentNode.props?.children === "string" ||
109
+ typeof contentNode.props?.children === "number"
110
+ ) {
111
+ // Clean the string by trimming and normalizing whitespace
112
+ result += contentNode.props.children
113
+ .toString()
114
+ .replace(/\s+/g, " ")
115
+ .trim();
116
+ } else if (
117
+ typeof contentNode === "string" ||
118
+ typeof contentNode === "number"
119
+ ) {
120
+ // Clean the string by trimming and normalizing whitespace
121
+ result += contentNode.toString().replace(/\s+/g, " ").trim();
96
122
  }
97
- });
98
- } else if (typeof contentNode.props?.children === "object") {
99
- result += buildCopyText(contentNode.props.children, false);
100
- } else if (typeof contentNode.props?.children === "string" ||
101
- typeof contentNode.props?.children === "number") {
102
- // Clean the string by trimming and normalizing whitespace
103
- result += contentNode.props.children.toString().replace(/\s+/g, ' ').trim();
104
- } else if (typeof contentNode === "string" || typeof contentNode === "number") {
105
- // Clean the string by trimming and normalizing whitespace
106
- result += contentNode.toString().replace(/\s+/g, ' ').trim();
107
- }
108
-
109
- return result;
110
- };
111
-
112
- // Process content and ensure we trim the final result
113
- const rawText = buildCopyText(content, false);
114
- return {
115
- errorCount: countUlLiChildren(content),
116
- copyText: rawText.replace(/\s+\n/g, '\n').replace(/\n\s+/g, '\n').trim()
117
- };
123
+
124
+ return result;
125
+ };
126
+
127
+ // Process content and ensure we trim the final result
128
+ const rawText = buildCopyText(content, false);
129
+ return {
130
+ errorCount: countUlLiChildren(content),
131
+ copyText: rawText
132
+ .replace(/\s+\n/g, "\n")
133
+ .replace(/\n\s+/g, "\n")
134
+ .trim(),
135
+ };
118
136
  };
119
137
 
120
138
  // Define ActionButton as a separate component outside MadieAlert
121
139
  const ActionButton = ({ tooltip, onClick, icon, testId, sx }) => (
122
- <Tooltip title={tooltip} data-testid={`${testId}-tooltip`} arrow>
123
- <IconButton
124
- onClick={onClick}
125
- sx={sx}
126
- data-testid={testId}
127
- >
128
- {icon}
129
- </IconButton>
130
- </Tooltip>
140
+ <Tooltip title={tooltip} data-testid={`${testId}-tooltip`} arrow>
141
+ <IconButton onClick={onClick} sx={sx} data-testid={testId}>
142
+ {icon}
143
+ </IconButton>
144
+ </Tooltip>
131
145
  );
132
146
 
133
147
  // Add PropTypes for ActionButton
134
148
  ActionButton.propTypes = {
135
- tooltip: PropTypes.string.isRequired,
136
- onClick: PropTypes.func.isRequired,
137
- icon: PropTypes.node.isRequired,
138
- testId: PropTypes.string.isRequired,
139
- sx: PropTypes.object
149
+ tooltip: PropTypes.string.isRequired,
150
+ onClick: PropTypes.func.isRequired,
151
+ icon: PropTypes.node.isRequired,
152
+ testId: PropTypes.string.isRequired,
153
+ sx: PropTypes.object,
140
154
  };
141
155
 
142
156
  const MadieAlert = ({
143
- type = "warning",
144
- visible = true,
145
- content,
146
- canClose = true,
147
- minimizeAlerts = false,
148
- alertProps,
149
- closeButtonProps,
150
- copyButton,
151
- alerts = null,
157
+ type = "warning",
158
+ visible = true,
159
+ content,
160
+ canClose = true,
161
+ minimizeAlerts = false,
162
+ alertProps,
163
+ closeButtonProps,
164
+ copyButton,
165
+ alerts = null,
152
166
  }) => {
153
- // Consolidate related state
154
- const [state, setState] = useState({
155
- toastOpen: false,
156
- copyText: "",
157
- minimizedAlerts: {},
158
- individualErrors: {},
159
- });
160
-
161
- // Create alias for readability
162
- const { toastOpen, copyText, minimizedAlerts, individualErrors } = state;
163
-
164
- // Use a single update function
165
- const updateState = (updates) => {
166
- setState(prev => ({ ...prev, ...updates }));
167
- };
168
-
169
- // Prepare alerts array once
170
- const alertsArray = useMemo(() => {
171
- return alerts || [{
172
- type, visible, content, canClose, alertProps, closeButtonProps, copyButton
173
- }];
174
- }, [alerts, type, visible, content, canClose, alertProps, closeButtonProps, copyButton]);
175
-
176
- // Process all alerts content once
177
- useEffect(() => {
178
- const newIndividualErrors = {};
179
- let totalCopyText = "";
180
-
181
- alertsArray.forEach((alert, index) => {
182
- if (alert.content && alert.visible !== false) {
183
- const processed = processContent(alert.content);
184
- newIndividualErrors[index] = processed.errorCount;
185
-
186
- if (alert.copyButton) {
187
- totalCopyText += processed.copyText + "\n\n";
188
- }
189
- }
190
- });
191
-
192
- updateState({
193
- individualErrors: newIndividualErrors,
194
- copyText: totalCopyText.trim(),
195
- });
196
- }, [alertsArray]);
197
-
198
- // Utility functions for managing minimized state
199
- const minimizeAlert = (index) => {
200
- updateState({
201
- minimizedAlerts: { ...minimizedAlerts, [index]: true }
167
+ // Consolidate related state
168
+ const [state, setState] = useState({
169
+ toastOpen: false,
170
+ copyText: "",
171
+ minimizedAlerts: {},
172
+ individualErrors: {},
202
173
  });
203
- };
204
-
205
- const restoreAllAlerts = () => {
206
- updateState({ minimizedAlerts: {} });
207
- };
208
-
209
- // Calculate minimized alerts info
210
- const minimizedIndices = Object.keys(minimizedAlerts)
211
- .filter(key => minimizedAlerts[key])
212
- .map(key => parseInt(key));
213
-
214
- const totalMinimizedErrors = minimizedIndices.reduce(
215
- (sum, index) => sum + (individualErrors[index] || 0),
216
- 0
217
- );
218
-
219
- // Render alert content
220
- const renderAlert = (alert, index) => {
221
- const {
222
- type = "warning",
223
- visible = true,
224
- content,
225
- canClose = true,
226
- alertProps = {},
227
- closeButtonProps = {},
228
- copyButton = false
229
- } = alert;
230
-
231
- if (!visible || minimizedAlerts[index]) return null;
232
-
233
- const Icon = typeToIconMap[type];
234
- const alertClass = classNames("madie-alert", type);
235
-
236
- return (
237
- <div key={index} className={alertClass} {...alertProps}>
238
- <Icon className={classNames("alert-icon", type)} />
239
- <div id="content" data-alert-index={index}>{content}</div>
240
-
241
- {minimizeAlerts && (
242
- <ActionButton
243
- tooltip="Minimize"
244
- onClick={(e) => {
245
- e.preventDefault();
246
- minimizeAlert(index);
247
- }}
248
- icon={<FullscreenExitRoundedIcon sx={{ color: "#242424" }} />}
249
- testId={`minimize-button-${index}`}
250
- sx={buttonWithDividerSx}
251
- />
252
- )}
253
-
254
- {copyButton && (
255
- <ActionButton
256
- tooltip="Copy"
257
- onClick={(e) => {
258
- e.preventDefault();
259
- navigator.clipboard.writeText(copyButton ? processContent(content).copyText : copyText);
260
- updateState({ toastOpen: true });
261
- }}
262
- icon={<ContentCopyIcon sx={{ color: "#242424" }} />}
263
- testId="copy-button"
264
- sx={!minimizeAlerts ? buttonWithDividerSx : buttonBaseSx}
265
- />
266
- )}
267
-
268
- {canClose && (
269
- <IconButton
270
- sx={buttonWithDividerSx}
271
- {...closeButtonProps}
272
- >
273
- <ClearIcon sx={{ color: "#242424" }} />
274
- </IconButton>
275
- )}
276
- </div>
174
+
175
+ // Create alias for readability
176
+ const { toastOpen, copyText, minimizedAlerts, individualErrors } = state;
177
+
178
+ // Use a single update function
179
+ const updateState = (updates) => {
180
+ setState((prev) => ({ ...prev, ...updates }));
181
+ };
182
+
183
+ // Prepare alerts array once
184
+ const alertsArray = useMemo(() => {
185
+ return (
186
+ alerts || [
187
+ {
188
+ type,
189
+ visible,
190
+ content,
191
+ canClose,
192
+ alertProps,
193
+ closeButtonProps,
194
+ copyButton,
195
+ },
196
+ ]
197
+ );
198
+ }, [
199
+ alerts,
200
+ type,
201
+ visible,
202
+ content,
203
+ canClose,
204
+ alertProps,
205
+ closeButtonProps,
206
+ copyButton,
207
+ ]);
208
+
209
+ // Process all alerts content once
210
+ useEffect(() => {
211
+ const newIndividualErrors = {};
212
+ let totalCopyText = "";
213
+
214
+ alertsArray.forEach((alert, index) => {
215
+ if (alert.content && alert.visible !== false) {
216
+ const processed = processContent(alert.content);
217
+ newIndividualErrors[index] = processed.errorCount;
218
+
219
+ if (alert.copyButton) {
220
+ totalCopyText += processed.copyText + "\n\n";
221
+ }
222
+ }
223
+ });
224
+
225
+ updateState({
226
+ individualErrors: newIndividualErrors,
227
+ copyText: totalCopyText.trim(),
228
+ });
229
+ }, [alertsArray]);
230
+
231
+ // Utility functions for managing minimized state
232
+ const minimizeAlert = (index) => {
233
+ updateState({
234
+ minimizedAlerts: { ...minimizedAlerts, [index]: true },
235
+ });
236
+ };
237
+
238
+ const restoreAllAlerts = () => {
239
+ updateState({ minimizedAlerts: {} });
240
+ };
241
+
242
+ // Calculate minimized alerts info
243
+ const minimizedIndices = Object.keys(minimizedAlerts)
244
+ .filter((key) => minimizedAlerts[key])
245
+ .map((key) => parseInt(key));
246
+
247
+ const totalMinimizedErrors = minimizedIndices.reduce(
248
+ (sum, index) => sum + (individualErrors[index] || 0),
249
+ 0
277
250
  );
278
- };
279
-
280
- return (
281
- <div>
282
- <Toast
283
- toastKey="copy-success-toast"
284
- data-testid="copy-success"
285
- toastType="success"
286
- open={toastOpen}
287
- message="Copied to clipboard!"
288
- onClose={() => updateState({ toastOpen: false })}
289
- autoHideDuration={1500}
290
- />
291
-
292
- {/* Render visible alerts */}
293
- {alertsArray.map((alert, index) => renderAlert(alert, index))}
294
-
295
- {/* Show minimized indicator if any alerts are minimized */}
296
- {minimizedIndices.length > 0 && (
297
- <div
298
- style={{
299
- position: "absolute",
300
- right: "32px",
301
- top: "10px",
302
- display: "flex",
303
- alignItems: "center",
304
- cursor: "pointer"
305
- }}
306
- data-testid="minimized-alert"
307
- onClick={(e) => {
308
- e.preventDefault();
309
- restoreAllAlerts();
310
- }}
311
- >
312
- <WarningRoundedIcon sx={{ color: "yellow", marginRight: "5px" }}/>
313
- <span data-testid="minimized-alert-text" style={{ color: "white", fontSize: "16px" }}>
314
- Display Alerts {totalMinimizedErrors > 0 ? `(${totalMinimizedErrors})` : ""}
315
- </span>
251
+
252
+ // Render alert content
253
+ const renderAlert = (alert, index) => {
254
+ const {
255
+ type = "warning",
256
+ visible = true,
257
+ content,
258
+ canClose = true,
259
+ alertProps = {},
260
+ closeButtonProps = {},
261
+ copyButton = false,
262
+ } = alert;
263
+
264
+ if (!visible || minimizedAlerts[index]) return null;
265
+
266
+ const Icon = typeToIconMap[type];
267
+ const alertClass = classNames("madie-alert", type);
268
+
269
+ return (
270
+ <div key={index} className={alertClass} {...alertProps}>
271
+ <Icon className={classNames("alert-icon", type)} />
272
+ <div id="content" data-alert-index={index}>
273
+ {content}
274
+ </div>
275
+
276
+ {minimizeAlerts && (
277
+ <ActionButton
278
+ tooltip="Minimize"
279
+ onClick={(e) => {
280
+ e.preventDefault();
281
+ minimizeAlert(index);
282
+ }}
283
+ icon={
284
+ <FullscreenExitRoundedIcon
285
+ sx={{ color: "#242424" }}
286
+ />
287
+ }
288
+ testId={`minimize-button-${index}`}
289
+ sx={buttonWithDividerSx}
290
+ />
291
+ )}
292
+
293
+ {copyButton && (
294
+ <ActionButton
295
+ tooltip="Copy"
296
+ onClick={(e) => {
297
+ e.preventDefault();
298
+ navigator.clipboard.writeText(
299
+ copyButton
300
+ ? processContent(content).copyText
301
+ : copyText
302
+ );
303
+ updateState({ toastOpen: true });
304
+ }}
305
+ icon={<ContentCopyIcon sx={{ color: "#242424" }} />}
306
+ testId="copy-button"
307
+ sx={
308
+ !minimizeAlerts ? buttonWithDividerSx : buttonBaseSx
309
+ }
310
+ />
311
+ )}
312
+
313
+ {canClose && (
314
+ <IconButton sx={buttonWithDividerSx} {...closeButtonProps}>
315
+ <ClearIcon sx={{ color: "#242424" }} />
316
+ </IconButton>
317
+ )}
318
+ </div>
319
+ );
320
+ };
321
+
322
+ return (
323
+ <div>
324
+ <Toast
325
+ toastKey="copy-success-toast"
326
+ data-testid="copy-success"
327
+ toastType="success"
328
+ open={toastOpen}
329
+ message="Copied to clipboard!"
330
+ onClose={() => updateState({ toastOpen: false })}
331
+ autoHideDuration={1500}
332
+ />
333
+
334
+ {/* Render visible alerts */}
335
+ {alertsArray.map((alert, index) => renderAlert(alert, index))}
336
+
337
+ {/* Show minimized indicator if any alerts are minimized */}
338
+ {minimizedIndices.length > 0 && (
339
+ <div
340
+ style={{
341
+ position: "absolute",
342
+ right: "32px",
343
+ top: "10px",
344
+ display: "flex",
345
+ alignItems: "center",
346
+ cursor: "pointer",
347
+ }}
348
+ data-testid="minimized-alert"
349
+ onClick={(e) => {
350
+ e.preventDefault();
351
+ restoreAllAlerts();
352
+ }}
353
+ >
354
+ <WarningRoundedIcon
355
+ sx={{ color: "yellow", marginRight: "5px" }}
356
+ />
357
+ <span
358
+ data-testid="minimized-alert-text"
359
+ style={{ color: "white", fontSize: "16px" }}
360
+ >
361
+ Display Alerts{" "}
362
+ {totalMinimizedErrors > 0
363
+ ? `(${totalMinimizedErrors})`
364
+ : ""}
365
+ </span>
366
+ </div>
367
+ )}
316
368
  </div>
317
- )}
318
- </div>
319
- );
369
+ );
320
370
  };
321
371
 
322
372
  // PropTypes definition
323
373
  MadieAlert.propTypes = {
324
- type: PropTypes.string,
325
- visible: PropTypes.bool,
326
- content: PropTypes.node,
327
- canClose: PropTypes.bool,
328
- alertProps: PropTypes.object,
329
- closeButtonProps: PropTypes.object,
330
- copyButton: PropTypes.bool,
331
- minimizeAlerts: PropTypes.bool,
332
- alerts: PropTypes.arrayOf(
333
- PropTypes.shape({
334
- type: PropTypes.string,
335
- visible: PropTypes.bool,
336
- content: PropTypes.node,
337
- canClose: PropTypes.bool,
338
- alertProps: PropTypes.object,
339
- closeButtonProps: PropTypes.object,
340
- copyButton: PropTypes.bool,
341
- })
342
- )
374
+ type: PropTypes.string,
375
+ visible: PropTypes.bool,
376
+ content: PropTypes.node,
377
+ canClose: PropTypes.bool,
378
+ alertProps: PropTypes.object,
379
+ closeButtonProps: PropTypes.object,
380
+ copyButton: PropTypes.bool,
381
+ minimizeAlerts: PropTypes.bool,
382
+ alerts: PropTypes.arrayOf(
383
+ PropTypes.shape({
384
+ type: PropTypes.string,
385
+ visible: PropTypes.bool,
386
+ content: PropTypes.node,
387
+ canClose: PropTypes.bool,
388
+ alertProps: PropTypes.object,
389
+ closeButtonProps: PropTypes.object,
390
+ copyButton: PropTypes.bool,
391
+ })
392
+ ),
343
393
  };
344
394
 
345
395
  MadieAlert.defaultProps = {
346
- copyButton: false,
347
- minimizeAlerts: false,
348
- alerts: null,
396
+ copyButton: false,
397
+ minimizeAlerts: false,
398
+ alerts: null,
349
399
  };
350
400
 
351
401
  export default MadieAlert;