@dashadmin/dash-components 1.3.25 → 1.3.28

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 (112) hide show
  1. package/README.md +35 -0
  2. package/dist/components/AuditLog/AuditLog.js +345 -1
  3. package/dist/components/AuditLog/index.js +4 -1
  4. package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +630 -3
  5. package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +53 -1
  6. package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +647 -3
  7. package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +53 -1
  8. package/dist/components/Json/Json.js +235 -1
  9. package/dist/components/JsonColorSelector/JsonColorSelector.js +1172 -1
  10. package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +863 -2
  11. package/dist/components/JsonColorSelector/JsonCssVarValues.js +738 -1
  12. package/dist/components/JsonColorSelector/components/ColorEditDialog.js +426 -1
  13. package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +75 -1
  14. package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +283 -1
  15. package/dist/components/JsonColorSelector/helpers/functions.js +101 -1
  16. package/dist/components/JsonColorSelector/useAsyncColorThief.js +105 -1
  17. package/dist/components/ListActive/ListActive.js +126 -1
  18. package/dist/components/ListActive/ListBoolean.js +124 -1
  19. package/dist/components/NotificationPreferences/NotificationPreferences.js +317 -1
  20. package/dist/components/NotificationPreferences/index.js +7 -1
  21. package/dist/components/SearchableSelects/RASearchableSelect.js +236 -1
  22. package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +479 -1
  23. package/dist/components/SearchableSelects/RASearchableSelectChips.js +465 -1
  24. package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +295 -1
  25. package/dist/components/SearchableSelects/SearchableSelect.js +202 -1
  26. package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +290 -1
  27. package/dist/components/SortableDataGrid/SortableDataGrid.js +72 -1
  28. package/dist/components/SortableDataGrid/SortableDataGridBody.js +167 -1
  29. package/dist/components/SortableDataGrid/SortableDataGridHeader.js +109 -1
  30. package/dist/components/SortableDataGrid/SortableDataGridRow.js +283 -1
  31. package/dist/components/Upload/SingleImageUploader.js +40 -1
  32. package/dist/components/custom/PackageCopyMethod.js +78 -1
  33. package/dist/components/custom/PackageRadioButtonGroup.js +45 -1
  34. package/dist/components/dialog/Basic.js +8 -1
  35. package/dist/components/dialog/Correct.js +14 -1
  36. package/dist/components/dialog/Error.js +14 -1
  37. package/dist/components/dialog/Info.js +14 -1
  38. package/dist/components/dialog/InfoV2.js +21 -1
  39. package/dist/components/dialog/NotFound.js +14 -1
  40. package/dist/components/dialog/QuickSearch.js +117 -1
  41. package/dist/components/notifications/NotificationsCenter.js +597 -1
  42. package/dist/components/notifications/index.js +24 -1
  43. package/dist/components/tables/PaginationComponent.js +36 -1
  44. package/dist/components/theme/AppLoadingFallback.js +38 -2
  45. package/dist/components/theme/ErrorBoundary.js +52 -1
  46. package/dist/components/theme/InitialLoader.js +13 -1
  47. package/dist/components/theme/NotFound.js +136 -1
  48. package/dist/components/theme/NotResults.js +38 -1
  49. package/dist/hooks/useDraggable.js +6 -1
  50. package/dist/hooks/useNotifications.js +8 -1
  51. package/dist/hooks/useQuickSearch.js +26 -1
  52. package/dist/index.js +66 -5567
  53. package/dist/utils/setNativeValue.js +17 -1
  54. package/package.json +148 -132
  55. package/src/components/AuditLog/AuditLog.tsx +476 -0
  56. package/src/components/AuditLog/index.ts +2 -0
  57. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +917 -0
  58. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +39 -0
  59. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +916 -0
  60. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +44 -0
  61. package/src/components/Json/Json.tsx +312 -0
  62. package/src/components/JsonColorSelector/JsonColorSelector.tsx +1435 -0
  63. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +1029 -0
  64. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +863 -0
  65. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +491 -0
  66. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +80 -0
  67. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +328 -0
  68. package/src/components/JsonColorSelector/helpers/functions.tsx +123 -0
  69. package/src/components/JsonColorSelector/interfaces/interfaces.tsx +22 -0
  70. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +97 -0
  71. package/src/components/ListActive/ListActive.tsx +106 -0
  72. package/src/components/ListActive/ListBoolean.tsx +96 -0
  73. package/src/components/NotificationPreferences/NotificationPreferences.tsx +463 -0
  74. package/src/components/NotificationPreferences/index.tsx +2 -0
  75. package/src/components/SearchableSelects/RASearchableSelect.tsx +300 -0
  76. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +604 -0
  77. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +558 -0
  78. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +362 -0
  79. package/src/components/SearchableSelects/SearchableSelect.tsx +321 -0
  80. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +355 -0
  81. package/src/components/SortableDataGrid/SortableDataGrid.tsx +56 -0
  82. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +175 -0
  83. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +171 -0
  84. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +247 -0
  85. package/src/components/Upload/SingleImageUploader.tsx +55 -0
  86. package/src/components/custom/PackageCopyMethod.tsx +109 -0
  87. package/src/components/custom/PackageRadioButtonGroup.tsx +47 -0
  88. package/src/components/dialog/Basic.tsx +11 -0
  89. package/src/components/dialog/Correct.tsx +16 -0
  90. package/src/components/dialog/Error.tsx +15 -0
  91. package/src/components/dialog/Info.tsx +15 -0
  92. package/src/components/dialog/InfoV2.tsx +35 -0
  93. package/src/components/dialog/NotFound.tsx +18 -0
  94. package/src/components/dialog/QuickSearch.tsx +115 -0
  95. package/src/components/notifications/NotificationsCenter.tsx +792 -0
  96. package/src/components/notifications/index.ts +42 -0
  97. package/src/components/tables/PaginationComponent.tsx +17 -0
  98. package/src/components/theme/AppLoadingFallback.tsx +42 -0
  99. package/src/components/theme/ErrorBoundary.tsx +57 -0
  100. package/src/components/theme/InitialLoader.tsx +16 -0
  101. package/src/components/theme/NotFound.tsx +124 -0
  102. package/src/components/theme/NotResults.tsx +54 -0
  103. package/src/hooks/useDraggable.ts +11 -0
  104. package/src/hooks/useNotifications.ts +14 -0
  105. package/src/hooks/useQuickSearch.tsx +29 -0
  106. package/src/index.tsx +77 -0
  107. package/src/utils/setNativeValue.tsx +23 -0
  108. package/dist/color-thief-CTwGFhOB.js +0 -272
  109. /package/{dist → src}/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -0
  110. /package/{dist → src}/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -0
  111. /package/{dist → src}/components/SortableDataGrid/README.md +0 -0
  112. /package/{dist → src}/styles/index.less +0 -0
@@ -0,0 +1,328 @@
1
+ import {
2
+ Box,
3
+ Button,
4
+ IconButton,
5
+ Typography,
6
+ Card,
7
+ CardMedia,
8
+ CircularProgress,
9
+ Input,
10
+ Alert,
11
+ TextField,
12
+ Divider,
13
+ } from '@mui/material';
14
+
15
+ import DeleteIcon from '@mui/icons-material/Delete';
16
+ import CloudUploadIcon from '@mui/icons-material/CloudUpload';
17
+ import PaletteIcon from '@mui/icons-material/Palette';
18
+ import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
19
+ import UpdateIcon from '@mui/icons-material/Update';
20
+ import { useState, useEffect, memo, useCallback } from 'react';
21
+ import { useAsyncColorThief } from '../useAsyncColorThief';
22
+ import { getContrastColor, rgbArrayToHex } from '../helpers/functions';
23
+ import { KeyValuePair } from '../interfaces/interfaces';
24
+ import { useAxios } from 'dash-axios-hook';
25
+
26
+ const ImageColorExtractor = memo<{
27
+ onColorsExtracted: (colors: number[][]) => void;
28
+ onColorsUpdate?: (newPairs: KeyValuePair[]) => void;
29
+ existingPairs?: KeyValuePair[];
30
+ }>(({ onColorsExtracted, onColorsUpdate, existingPairs = [] }) => {
31
+ const [imageUrl, setImageUrl] = useState<string | null>(null);
32
+ const [uploadedFile, setUploadedFile] = useState<File | null>(null);
33
+ const [themePrompt, setThemePrompt] = useState<string>('');
34
+ const [isGeneratingTheme, setIsGeneratingTheme] = useState<boolean>(false);
35
+ const [themeError, setThemeError] = useState<string | null>(null);
36
+
37
+ // Add state to preserve extracted colors independently
38
+ const [extractedPalette, setExtractedPalette] = useState<number[][]>([]);
39
+
40
+ const axios = useAxios();
41
+ const { dominantColor, palette, loading, error } = useAsyncColorThief(imageUrl, {
42
+ colorCount: 8,
43
+ quality: 10
44
+ });
45
+
46
+ // Update internal extracted palette state
47
+ useEffect(() => {
48
+ if (palette && palette.length > 0) {
49
+ setExtractedPalette(palette);
50
+ onColorsExtracted(palette);
51
+ }
52
+ }, [palette, onColorsExtracted]);
53
+
54
+ // Handle file selection for upload
55
+ const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
56
+ const file = event.target.files?.[0];
57
+ if (file) {
58
+ // Validate file type
59
+ if (!file.type.startsWith('image/')) {
60
+ alert('Please select a valid image file');
61
+ return;
62
+ }
63
+
64
+ // Validate file size (e.g., max 5MB)
65
+ if (file.size > 5 * 1024 * 1024) {
66
+ alert('File size must be less than 5MB');
67
+ return;
68
+ }
69
+
70
+ setUploadedFile(file);
71
+
72
+ // Create preview URL
73
+ const previewUrl = URL.createObjectURL(file);
74
+ setImageUrl(previewUrl);
75
+ }
76
+ };
77
+
78
+ // Handle image removal
79
+ const handleImageRemove = () => {
80
+ if (imageUrl && imageUrl.startsWith('blob:')) {
81
+ URL.revokeObjectURL(imageUrl);
82
+ }
83
+
84
+ setImageUrl(null);
85
+ setUploadedFile(null);
86
+ setExtractedPalette([]); // Clear internal palette state
87
+ onColorsExtracted([]); // Clear extracted colors
88
+ };
89
+
90
+ // Generate theme using AI - Modified to merge with existing colors
91
+ const handleGenerateTheme = useCallback(async () => {
92
+ if (!extractedPalette || extractedPalette.length === 0) {
93
+ setThemeError('Please extract colors from an image first');
94
+ return;
95
+ }
96
+
97
+ setIsGeneratingTheme(true);
98
+ setThemeError(null);
99
+
100
+ try {
101
+ const response = await axios.post('/tenant/tenant/settings/theme-generator', {
102
+ colors: extractedPalette,
103
+ prompt: themePrompt,
104
+ output_format: 'json'
105
+ });
106
+
107
+ if (response.status !== 200) {
108
+ throw new Error(`HTTP error! status: ${response.status}`);
109
+ }
110
+
111
+ const result = response.data;
112
+
113
+ if (result.success && result.data) {
114
+ // Convert the generated theme to KeyValuePair format
115
+ const newPairs: KeyValuePair[] = Object.entries(result.data).map(([key, value]) => ({
116
+ key,
117
+ value: String(value),
118
+ id: Math.random().toString(36).substring(2, 11)
119
+ }));
120
+
121
+ // Merge with existing pairs instead of replacing them
122
+ const mergedPairs = [...existingPairs];
123
+
124
+ newPairs.forEach(newPair => {
125
+ const existingIndex = mergedPairs.findIndex(existing => existing.key === newPair.key);
126
+ if (existingIndex >= 0) {
127
+ // Update existing pair
128
+ mergedPairs[existingIndex] = newPair;
129
+ } else {
130
+ // Add new pair
131
+ mergedPairs.push(newPair);
132
+ }
133
+ });
134
+
135
+ // Update the colors in the parent component
136
+ if (onColorsUpdate) {
137
+ onColorsUpdate(mergedPairs);
138
+ }
139
+ } else {
140
+ throw new Error(result.message || 'Failed to generate theme');
141
+ }
142
+ } catch (error) {
143
+ console.error('Theme generation error:', error);
144
+ setThemeError(error instanceof Error ? error.message : 'Failed to generate theme');
145
+ } finally {
146
+ setIsGeneratingTheme(false);
147
+ }
148
+ }, [extractedPalette, themePrompt, existingPairs, onColorsUpdate, axios]);
149
+
150
+ return (
151
+ <Box sx={{ mt: 3, p: 3, borderRadius: 2 }}>
152
+ <Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
153
+ <PaletteIcon />
154
+ AI Theme Generator
155
+ </Typography>
156
+
157
+ <Divider sx={{ mb: 3 }} />
158
+
159
+ {/* Image Upload Section */}
160
+ <Box sx={{ mb: 3 }}>
161
+ {!imageUrl ? (
162
+ <Box sx={{
163
+ textAlign: 'center',
164
+ py: 4,
165
+ backgroundColor: 'rgba(0,0,0,0.02)',
166
+ borderRadius: 1,
167
+ border: '2px dashed #e0e0e0'
168
+ }}>
169
+ <Input
170
+ type="file"
171
+ inputProps={{ accept: 'image/*' }}
172
+ onChange={handleFileSelect}
173
+ sx={{ display: 'none' }}
174
+ id="color-extractor-upload"
175
+ />
176
+ <Typography variant="body1" color="textSecondary" gutterBottom>
177
+ Upload an Image to Extract Colors
178
+ </Typography>
179
+ <Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
180
+ Select an image file and we'll automatically extract a color palette
181
+ </Typography>
182
+ <label htmlFor="color-extractor-upload">
183
+ <Button
184
+ variant="contained"
185
+ component="span"
186
+ startIcon={<CloudUploadIcon />}
187
+ size="large"
188
+ >
189
+ Choose Image
190
+ </Button>
191
+ </label>
192
+ </Box>
193
+ ) : (
194
+ <Card sx={{ maxWidth: 400, mb: 3 }}>
195
+ <Box sx={{ position: 'relative' }}>
196
+ <CardMedia
197
+ component="img"
198
+ height="200"
199
+ image={imageUrl}
200
+ alt="Uploaded image"
201
+ sx={{ objectFit: 'contain' }}
202
+ />
203
+ <IconButton
204
+ sx={{
205
+ position: 'absolute',
206
+ top: 8,
207
+ right: 8,
208
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
209
+ '&:hover': {
210
+ backgroundColor: 'rgba(255, 255, 255, 1)',
211
+ },
212
+ }}
213
+ onClick={handleImageRemove}
214
+ size="small"
215
+ >
216
+ <DeleteIcon />
217
+ </IconButton>
218
+ </Box>
219
+ </Card>
220
+ )}
221
+ </Box>
222
+
223
+ {/* Color Extraction Status */}
224
+ {loading && (
225
+ <Box display="flex" alignItems="center" gap={2} sx={{ mb: 3, p: 2, backgroundColor: 'rgba(25, 118, 210, 0.04)', borderRadius: 1 }}>
226
+ <CircularProgress size={20} />
227
+ <Typography>Extracting colors from image...</Typography>
228
+ </Box>
229
+ )}
230
+
231
+ {error && (
232
+ <Alert severity="error" sx={{ mb: 3 }}>
233
+ {error}
234
+ </Alert>
235
+ )}
236
+
237
+ {themeError && (
238
+ <Alert severity="error" sx={{ mb: 3 }}>
239
+ {themeError}
240
+ </Alert>
241
+ )}
242
+
243
+ {/* Extracted Colors Display - Use internal state */}
244
+ {extractedPalette && extractedPalette.length > 0 && !loading && (
245
+ <Box sx={{ mb: 3 }}>
246
+ <Typography variant="subtitle1" gutterBottom>
247
+ Extracted Colors ({extractedPalette.length} colors found)
248
+ </Typography>
249
+
250
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2, mb: 3 }}>
251
+ {extractedPalette.map((color, index) => {
252
+ const hexColor = rgbArrayToHex(color);
253
+ const isDominant = dominantColor &&
254
+ color[0] === dominantColor[0] &&
255
+ color[1] === dominantColor[1] &&
256
+ color[2] === dominantColor[2];
257
+
258
+ return (
259
+ <Box
260
+ key={index}
261
+ sx={{
262
+ width: 50,
263
+ height: 50,
264
+ backgroundColor: hexColor,
265
+ borderRadius: '50%',
266
+ display: 'flex',
267
+ alignItems: 'center',
268
+ justifyContent: 'center',
269
+ border: isDominant ? '3px solid #4caf50' : '2px solid #ccc',
270
+ cursor: 'pointer',
271
+ transition: 'transform 0.2s',
272
+ '&:hover': {
273
+ transform: 'scale(1.1)',
274
+ },
275
+ }}
276
+ title={`${hexColor} ${isDominant ? '(Dominant)' : ''}`}
277
+ >
278
+ <Typography
279
+ variant="caption"
280
+ sx={{
281
+ color: getContrastColor(hexColor),
282
+ fontWeight: 'bold',
283
+ fontSize: '0.8rem',
284
+ }}
285
+ >
286
+ {index + 1}
287
+ </Typography>
288
+ </Box>
289
+ );
290
+ })}
291
+ </Box>
292
+
293
+ {/* Theme Generation Controls */}
294
+ <Box sx={{ mb: 3 }}>
295
+ <TextField
296
+ fullWidth
297
+ multiline
298
+ rows={2}
299
+ placeholder="Optional: Describe the theme style you want (e.g., 'modern and minimalist', 'warm and cozy', 'professional corporate')"
300
+ value={themePrompt}
301
+ onChange={(e) => setThemePrompt(e.target.value)}
302
+ sx={{ mb: 2 }}
303
+ label="Theme Description (Optional)"
304
+ />
305
+
306
+ <Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
307
+ <Button
308
+ variant="contained"
309
+ startIcon={isGeneratingTheme ? <CircularProgress size={16} /> : <AutoFixHighIcon />}
310
+ onClick={handleGenerateTheme}
311
+ disabled={isGeneratingTheme}
312
+ color="primary"
313
+ >
314
+ {isGeneratingTheme ? 'Generating...' : 'Generate AI Theme'}
315
+ </Button>
316
+ </Box>
317
+ </Box>
318
+
319
+ <Typography variant="body2" color="textSecondary" sx={{ fontStyle: 'italic' }}>
320
+ • <strong>Generate AI Theme:</strong> Uses AI to create a complete color scheme based on extracted colors and your description
321
+ </Typography>
322
+ </Box>
323
+ )}
324
+ </Box>
325
+ );
326
+ });
327
+
328
+ export default ImageColorExtractor;
@@ -0,0 +1,123 @@
1
+ import { ColorFormat, ColorFormatType, KeyValuePair } from "../interfaces/interfaces";
2
+
3
+ // Helper function to determine text color based on background
4
+ export const getContrastColor = (color: string): string => {
5
+ // Handle different color formats
6
+ let r: number, g: number, b: number, a: number = 1;
7
+
8
+ if (color.startsWith('#')) {
9
+ // Hex color
10
+ const hex = color.replace('#', '');
11
+ if (hex.length === 8) {
12
+ // RGBA hex
13
+ r = parseInt(hex.substr(0, 2), 16);
14
+ g = parseInt(hex.substr(2, 2), 16);
15
+ b = parseInt(hex.substr(4, 2), 16);
16
+ a = parseInt(hex.substr(6, 2), 16) / 255;
17
+ } else {
18
+ // RGB hex
19
+ r = parseInt(hex.substr(0, 2), 16);
20
+ g = parseInt(hex.substr(2, 2), 16);
21
+ b = parseInt(hex.substr(4, 2), 16);
22
+ }
23
+ } else if (color.startsWith('rgb')) {
24
+ // RGB/RGBA color
25
+ const match = color.match(/rgba?\(([^)]+)\)/);
26
+ if (match) {
27
+ const values = match[1].split(',').map(v => parseFloat(v.trim()));
28
+ [r, g, b, a = 1] = values;
29
+ } else {
30
+ return '#000000';
31
+ }
32
+ } else if (color.startsWith('hsl')) {
33
+ // For HSL, we'll use a simple approach
34
+ return '#000000';
35
+ } else {
36
+ return '#000000';
37
+ }
38
+
39
+ // Calculate luminance considering alpha
40
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
41
+ const effectiveLuminance = luminance * a + (1 - a); // Blend with white background
42
+
43
+ return effectiveLuminance > 0.5 ? '#000000' : '#ffffff';
44
+ };
45
+
46
+ // Helper function to convert color to different formats
47
+ // Helper function to convert color to different formats - Updated
48
+ export const convertColor = (color: ColorFormat, format: ColorFormatType): string => {
49
+ switch (format) {
50
+ case 'hex':
51
+ // For hex, we'll ignore alpha or convert to 8-digit hex if alpha < 1
52
+ if (color.rgb.a !== undefined && color.rgb.a < 1) {
53
+ const alpha = Math.round(color.rgb.a * 255);
54
+ const alphaHex = alpha.toString(16).padStart(2, '0');
55
+ return `${color.hex}${alphaHex}`;
56
+ }
57
+ return color.hex;
58
+ case 'rgb':
59
+ return `rgb(${Math.round(color.rgb.r)}, ${Math.round(color.rgb.g)}, ${Math.round(color.rgb.b)})`;
60
+ case 'rgba':
61
+ const alpha = color.rgb.a !== undefined ? Number(color.rgb.a.toFixed(2)) : 1;
62
+ return `rgba(${Math.round(color.rgb.r)}, ${Math.round(color.rgb.g)}, ${Math.round(color.rgb.b)}, ${alpha})`;
63
+ case 'hsl':
64
+ return `hsl(${Math.round(color.hsl.h)}, ${Math.round(color.hsl.s * 100)}%, ${Math.round(color.hsl.l * 100)}%)`;
65
+ case 'hsla':
66
+ const hslaAlpha = color.hsl.a !== undefined ? Number(color.hsl.a.toFixed(2)) : 1;
67
+ return `hsla(${Math.round(color.hsl.h)}, ${Math.round(color.hsl.s * 100)}%, ${Math.round(color.hsl.l * 100)}%, ${hslaAlpha})`;
68
+ default:
69
+ return color.hex;
70
+ }
71
+ };
72
+ // Helper function to parse color key and create chips
73
+ export const parseColorKey = (key: string) => {
74
+ const parts = key.split('--');
75
+ const baseName = parts[0];
76
+ const mode = parts[1];
77
+
78
+ // Split base name by hyphens for individual chips
79
+ const baseChips = baseName.split('-').filter(part => part.length > 0);
80
+
81
+ return { baseChips, mode };
82
+ };
83
+
84
+ // Helper function to extract available modes from color keys
85
+ export const extractAvailableModes = (pairs: KeyValuePair[]): string[] => {
86
+ const modes = new Set<string>();
87
+
88
+ pairs.forEach(pair => {
89
+ const { mode } = parseColorKey(pair.key);
90
+ if (mode) {
91
+ modes.add(mode);
92
+ }
93
+ });
94
+
95
+ return Array.from(modes).sort();
96
+ };
97
+
98
+ // Helper function to get mode icon
99
+ export const getModeIcon = (mode: string | undefined): string => {
100
+ if (!mode) return '';
101
+
102
+ switch (mode.toLowerCase()) {
103
+ case 'light':
104
+ return '☀️'; // Sun icon
105
+ case 'dark':
106
+ return '🌙'; // Moon icon
107
+ default:
108
+ return mode; // Plain text for other modes
109
+ }
110
+ };
111
+
112
+ // Helper function to convert RGB array to hex
113
+ export const rgbArrayToHex = (rgb: number[]): string => {
114
+ const [r, g, b] = rgb;
115
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
116
+ };
117
+
118
+ // Helper function to convert RGB array to rgb string
119
+ export const rgbArrayToRgbString = (rgb: number[]): string => {
120
+ const [r, g, b] = rgb;
121
+ return `rgb(${r}, ${g}, ${b})`;
122
+ };
123
+
@@ -0,0 +1,22 @@
1
+
2
+ import { RGBColor } from 'react-color';
3
+
4
+
5
+ export interface KeyValuePair {
6
+ key: string;
7
+ value: string;
8
+ id: string;
9
+ }
10
+
11
+ export interface ColorFormat {
12
+ hex: string;
13
+ rgb: RGBColor;
14
+ hsl: { h: number; s: number; l: number; a?: number };
15
+ }
16
+
17
+ export type ColorFormatType = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla';
18
+
19
+ // Color mapping interface
20
+ export interface ColorMapping {
21
+ [key: string]: string;
22
+ }
@@ -0,0 +1,97 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ interface ColorThiefResult {
4
+ dominantColor: number[] | null;
5
+ palette: number[][] | null;
6
+ loading: boolean;
7
+ error: string | null;
8
+ }
9
+
10
+ /**
11
+ * Custom hook for async loading and using Color Thief functionality
12
+ * @param imageSource - The image source (URL, base64, or ref)
13
+ * @param options - Configuration options for color extraction
14
+ * @returns Color thief result with dominant color and palette
15
+ */
16
+ export const useAsyncColorThief = (
17
+ imageSource: string | null,
18
+ options: { colorCount?: number; quality?: number } = {}
19
+ ): ColorThiefResult => {
20
+ const [result, setResult] = useState<ColorThiefResult>({
21
+ dominantColor: null,
22
+ palette: null,
23
+ loading: false,
24
+ error: null
25
+ });
26
+
27
+ const extractColors = useCallback(async (src: string) => {
28
+ if (!src) return;
29
+
30
+ setResult(prev => ({ ...prev, loading: true, error: null }));
31
+
32
+ try {
33
+ // Dynamically import ColorThief
34
+ const ColorThief = (await import('colorthief')).default;
35
+
36
+ // Create a temporary image element for color extraction
37
+ const img = new Image();
38
+ img.crossOrigin = 'anonymous';
39
+
40
+ img.onload = () => {
41
+ try {
42
+ const colorThief = new ColorThief();
43
+
44
+ const dominantColor = colorThief.getColor(img);
45
+ const palette = colorThief.getPalette(img, options.colorCount || 5, options.quality || 10);
46
+
47
+ setResult({
48
+ dominantColor,
49
+ palette,
50
+ loading: false,
51
+ error: null
52
+ });
53
+ } catch (error) {
54
+ console.error('Color extraction error:', error);
55
+ setResult(prev => ({
56
+ ...prev,
57
+ loading: false,
58
+ error: `Failed to extract colors: ${error instanceof Error ? error.message : 'Unknown error'}`
59
+ }));
60
+ }
61
+ };
62
+
63
+ img.onerror = () => {
64
+ setResult(prev => ({
65
+ ...prev,
66
+ loading: false,
67
+ error: 'Failed to load image'
68
+ }));
69
+ };
70
+
71
+ img.src = src;
72
+ } catch (error) {
73
+ console.error('ColorThief import error:', error);
74
+ setResult(prev => ({
75
+ ...prev,
76
+ loading: false,
77
+ error: `Failed to load Color Thief: ${error instanceof Error ? error.message : 'Unknown error'}`
78
+ }));
79
+ }
80
+ }, [options.colorCount, options.quality]);
81
+
82
+ useEffect(() => {
83
+ if (imageSource) {
84
+ extractColors(imageSource);
85
+ } else {
86
+ // Reset state when no image source
87
+ setResult({
88
+ dominantColor: null,
89
+ palette: null,
90
+ loading: false,
91
+ error: null
92
+ });
93
+ }
94
+ }, [imageSource, extractColors]);
95
+
96
+ return result;
97
+ };
@@ -0,0 +1,106 @@
1
+ import React, { act, useCallback, useEffect, useState } from 'react';
2
+ import { IDashAutoAdminCustomFieldComponent } from 'dash-auto-admin';
3
+ /*import { ICommunes } from '@app/interfaces/ICommunes'; */
4
+ import { useRecordContext, useUpdate } from 'react-admin';
5
+ import { Switch, Box } from '@mui/material';
6
+ import { FormControlLabel } from '@mui/material';
7
+ import { AttributeToField } from 'dash-auto-admin/src/mui/AttributeToField';
8
+ import AttributeToInput from 'dash-auto-admin/src/mui/AttributeToInput';
9
+
10
+ export interface IListActive extends IDashAutoAdminCustomFieldComponent {
11
+ activeLabel?: string;
12
+ inactiveLabel?: string;
13
+ enableLabel?: string;
14
+ disableLabel?: string;
15
+ }
16
+
17
+ const ListActive = ({
18
+ method,
19
+ attribute,
20
+ resourceConfig,
21
+ activeLabel = 'Activo',
22
+ inactiveLabel = 'Inactivo',
23
+ enableLabel = 'Activar',
24
+ disableLabel = 'Desactivar',
25
+ showLabel = false,
26
+ }: IListActive) => {
27
+ const [ ,setLoading] = useState<boolean>(true);
28
+ const [update, { isLoading: updateLoading }] = useUpdate();
29
+ const record: any = useRecordContext();
30
+ const [is_active, setIsActive] = useState<boolean>(false);
31
+ const onError = (error: any) => {
32
+ console.error(error);
33
+ };
34
+
35
+ const onChange = useCallback(
36
+ async (value: boolean) => {
37
+ /*window.dispatchEvent(
38
+ new MessageEvent('dash-global-loader', { data: true })
39
+ );*/
40
+
41
+ try {
42
+ await update(
43
+ resourceConfig.model + '/partial/' + record.id,
44
+ {
45
+ id: record.id,
46
+ data: { [attribute.attribute]: value },
47
+ previousData: record,
48
+ meta: {
49
+ method: 'POST',
50
+ }
51
+ },
52
+
53
+ {
54
+ onSuccess: () => {
55
+ // Force a refresh of the record context
56
+ setIsActive(value);
57
+ },
58
+ onSettled: (data, error) => {
59
+ if (error) onError(error);
60
+ /* window.dispatchEvent(
61
+ new MessageEvent('dash-global-loader', { data: false })
62
+ );*/
63
+ },
64
+ }
65
+ );
66
+ } catch (error) {
67
+ onError(error);
68
+ }
69
+ },
70
+ [update, record]
71
+ );
72
+
73
+ useEffect(() => {
74
+ setLoading(false);
75
+
76
+ if (record && attribute && attribute.attribute in record) {
77
+ setIsActive(!!record[attribute.attribute]);
78
+ }
79
+ }, [record, attribute]);
80
+
81
+ //const textSwitch = record[attribute.attribute] ? disableLabel : enableLabel;
82
+
83
+ switch (method) {
84
+ case 'create':
85
+ case 'edit':
86
+ return AttributeToInput(method, resourceConfig, {...attribute,custom:false,type:Boolean})
87
+ case 'view':
88
+ return AttributeToField(method, resourceConfig, {...attribute,custom:false,type:Boolean})
89
+ case 'list':
90
+ return (
91
+ <div>
92
+ <Box>
93
+ <Switch
94
+ size='small'
95
+ onChange={(e, value) => onChange(value)}
96
+ checked={is_active}
97
+ />{showLabel ? (is_active ? activeLabel : inactiveLabel) : <></>}
98
+ </Box>
99
+ </div>
100
+ )
101
+ default:
102
+ return <>{attribute.label}: {is_active ? activeLabel : inactiveLabel}</>;
103
+ }
104
+ };
105
+
106
+ export default ListActive;