@dashadmin/dash-components 1.3.72 → 1.3.74-srcfree.1

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 (60) hide show
  1. package/package.json +24 -21
  2. package/src/components/AuditLog/AuditLog.tsx +0 -490
  3. package/src/components/AuditLog/index.ts +0 -2
  4. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +0 -917
  5. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +0 -39
  6. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +0 -916
  7. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -42
  8. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +0 -44
  9. package/src/components/Json/Json.tsx +0 -312
  10. package/src/components/JsonColorSelector/JsonColorSelector.tsx +0 -1443
  11. package/src/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -746
  12. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +0 -1135
  13. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +0 -867
  14. package/src/components/JsonColorSelector/components/BasicColorSelector.tsx +0 -333
  15. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +0 -495
  16. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +0 -80
  17. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +0 -420
  18. package/src/components/JsonColorSelector/helpers/functions.tsx +0 -123
  19. package/src/components/JsonColorSelector/helpers/paletteDerivation.ts +0 -367
  20. package/src/components/JsonColorSelector/interfaces/interfaces.tsx +0 -22
  21. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +0 -97
  22. package/src/components/ListActive/ListActive.tsx +0 -106
  23. package/src/components/ListActive/ListBoolean.tsx +0 -96
  24. package/src/components/NotificationPreferences/NotificationPreferences.tsx +0 -480
  25. package/src/components/NotificationPreferences/index.tsx +0 -2
  26. package/src/components/SearchableSelects/RASearchableSelect.tsx +0 -303
  27. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +0 -617
  28. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +0 -561
  29. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +0 -366
  30. package/src/components/SearchableSelects/SearchableSelect.tsx +0 -323
  31. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +0 -358
  32. package/src/components/SortableDataGrid/README.md +0 -1
  33. package/src/components/SortableDataGrid/SortableDataGrid.tsx +0 -56
  34. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +0 -175
  35. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +0 -171
  36. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +0 -247
  37. package/src/components/Upload/SingleImageUploader.tsx +0 -55
  38. package/src/components/custom/PackageCopyMethod.tsx +0 -109
  39. package/src/components/custom/PackageRadioButtonGroup.tsx +0 -49
  40. package/src/components/dialog/Basic.tsx +0 -11
  41. package/src/components/dialog/Correct.tsx +0 -16
  42. package/src/components/dialog/Error.tsx +0 -15
  43. package/src/components/dialog/Info.tsx +0 -15
  44. package/src/components/dialog/InfoV2.tsx +0 -35
  45. package/src/components/dialog/NotFound.tsx +0 -18
  46. package/src/components/dialog/QuickSearch.tsx +0 -115
  47. package/src/components/notifications/NotificationsCenter.tsx +0 -743
  48. package/src/components/notifications/index.ts +0 -42
  49. package/src/components/tables/PaginationComponent.tsx +0 -17
  50. package/src/components/theme/AppLoadingFallback.tsx +0 -42
  51. package/src/components/theme/ErrorBoundary.tsx +0 -57
  52. package/src/components/theme/InitialLoader.tsx +0 -16
  53. package/src/components/theme/NotFound.tsx +0 -124
  54. package/src/components/theme/NotResults.tsx +0 -56
  55. package/src/hooks/useDraggable.ts +0 -11
  56. package/src/hooks/useNotifications.ts +0 -14
  57. package/src/hooks/useQuickSearch.tsx +0 -29
  58. package/src/index.tsx +0 -77
  59. package/src/styles/index.less +0 -0
  60. package/src/utils/setNativeValue.tsx +0 -23
@@ -1,420 +0,0 @@
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 { useTranslate } from 'react-admin';
22
- import { useAsyncColorThief } from '../useAsyncColorThief';
23
- import { getContrastColor, rgbArrayToHex } from '../helpers/functions';
24
- import { KeyValuePair } from '../interfaces/interfaces';
25
- import { useAxios } from 'dash-axios-hook';
26
-
27
- const MAX_IMAGE_MB = 5;
28
-
29
- const ImageColorExtractor = memo<{
30
- onColorsExtracted: (colors: number[][]) => void;
31
- onColorsUpdate?: (newPairs: KeyValuePair[]) => void;
32
- existingPairs?: KeyValuePair[];
33
- /** When false, the OpenAI theme generation UI is hidden — only local extraction applies. */
34
- aiEnabled?: boolean;
35
- /** Local (no-AI) apply: receives the extracted RGB palette so the caller can map it to base colors. */
36
- onApplyLocal?: (palette: number[][]) => void;
37
- }>(({ onColorsExtracted, onColorsUpdate, existingPairs = [], aiEnabled = true, onApplyLocal }) => {
38
- const translate = useTranslate();
39
- const [imageUrl, setImageUrl] = useState<string | null>(null);
40
- const [uploadedFile, setUploadedFile] = useState<File | null>(null);
41
- const [themePrompt, setThemePrompt] = useState<string>('');
42
- const [isGeneratingTheme, setIsGeneratingTheme] = useState<boolean>(false);
43
- const [themeError, setThemeError] = useState<string | null>(null);
44
- const [isDragOver, setIsDragOver] = useState<boolean>(false);
45
-
46
- // Add state to preserve extracted colors independently
47
- const [extractedPalette, setExtractedPalette] = useState<number[][]>([]);
48
-
49
- const axios = useAxios();
50
- const { dominantColor, palette, loading, error } = useAsyncColorThief(imageUrl, {
51
- colorCount: 8,
52
- quality: 10
53
- });
54
-
55
- // Update internal extracted palette state
56
- useEffect(() => {
57
- if (palette && palette.length > 0) {
58
- setExtractedPalette(palette);
59
- onColorsExtracted(palette);
60
- }
61
- }, [palette, onColorsExtracted]);
62
-
63
- // Shared validation + preview setup for a file coming from either the file
64
- // input or a drag-and-drop, so the two paths can never drift apart.
65
- const processFile = useCallback((file: File) => {
66
- // Validate file type
67
- if (!file.type.startsWith('image/')) {
68
- alert(translate('colorSelector.imageExtractor.invalid_file_type'));
69
- return;
70
- }
71
-
72
- // Validate file size
73
- if (file.size > MAX_IMAGE_MB * 1024 * 1024) {
74
- alert(translate('colorSelector.imageExtractor.file_too_large', { limit: MAX_IMAGE_MB }));
75
- return;
76
- }
77
-
78
- setUploadedFile(file);
79
-
80
- // Create preview URL
81
- const previewUrl = URL.createObjectURL(file);
82
- setImageUrl(previewUrl);
83
- }, [translate]);
84
-
85
- // Handle file selection for upload
86
- const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
87
- const file = event.target.files?.[0];
88
- if (file) {
89
- processFile(file);
90
- }
91
- };
92
-
93
- // Drag-and-drop onto the upload dropzone
94
- const handleDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => {
95
- event.preventDefault();
96
- event.stopPropagation();
97
- setIsDragOver(true);
98
- }, []);
99
-
100
- const handleDragLeave = useCallback((event: React.DragEvent<HTMLDivElement>) => {
101
- event.preventDefault();
102
- event.stopPropagation();
103
- setIsDragOver(false);
104
- }, []);
105
-
106
- const handleDrop = useCallback((event: React.DragEvent<HTMLDivElement>) => {
107
- event.preventDefault();
108
- event.stopPropagation();
109
- setIsDragOver(false);
110
-
111
- const file = event.dataTransfer.files?.[0];
112
- if (file) {
113
- processFile(file);
114
- }
115
- }, [processFile]);
116
-
117
- // Handle image removal
118
- const handleImageRemove = () => {
119
- if (imageUrl && imageUrl.startsWith('blob:')) {
120
- URL.revokeObjectURL(imageUrl);
121
- }
122
-
123
- setImageUrl(null);
124
- setUploadedFile(null);
125
- setExtractedPalette([]); // Clear internal palette state
126
- onColorsExtracted([]); // Clear extracted colors
127
- };
128
-
129
- // Generate theme using AI - Modified to merge with existing colors
130
- const handleGenerateTheme = useCallback(async () => {
131
- if (!extractedPalette || extractedPalette.length === 0) {
132
- setThemeError(translate('colorSelector.imageExtractor.no_palette_error'));
133
- return;
134
- }
135
-
136
- setIsGeneratingTheme(true);
137
- setThemeError(null);
138
-
139
- try {
140
- const response = await axios.post('/tenant/tenant/settings/theme-generator', {
141
- colors: extractedPalette,
142
- prompt: themePrompt,
143
- output_format: 'json'
144
- });
145
-
146
- if (response.status !== 200) {
147
- throw new Error(`HTTP error! status: ${response.status}`);
148
- }
149
-
150
- const result = response.data;
151
-
152
- if (result.success && result.data) {
153
- // Convert the generated theme to KeyValuePair format
154
- const newPairs: KeyValuePair[] = Object.entries(result.data).map(([key, value]) => ({
155
- key,
156
- value: String(value),
157
- id: Math.random().toString(36).substring(2, 11)
158
- }));
159
-
160
- // Merge with existing pairs instead of replacing them
161
- const mergedPairs = [...existingPairs];
162
-
163
- newPairs.forEach(newPair => {
164
- const existingIndex = mergedPairs.findIndex(existing => existing.key === newPair.key);
165
- if (existingIndex >= 0) {
166
- // Update existing pair
167
- mergedPairs[existingIndex] = newPair;
168
- } else {
169
- // Add new pair
170
- mergedPairs.push(newPair);
171
- }
172
- });
173
-
174
- // Update the colors in the parent component
175
- if (onColorsUpdate) {
176
- onColorsUpdate(mergedPairs);
177
- }
178
- } else {
179
- throw new Error(result.message || translate('colorSelector.imageExtractor.theme_generation_failed'));
180
- }
181
- } catch (error) {
182
- console.error('Theme generation error:', error);
183
- setThemeError(
184
- error instanceof Error
185
- ? error.message
186
- : translate('colorSelector.imageExtractor.theme_generation_failed')
187
- );
188
- } finally {
189
- setIsGeneratingTheme(false);
190
- }
191
- }, [extractedPalette, themePrompt, existingPairs, onColorsUpdate, axios, translate]);
192
-
193
- return (
194
- <Box sx={{ mt: 3, p: 3, borderRadius: 2 }}>
195
- <Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
196
- <PaletteIcon />
197
- {aiEnabled
198
- ? translate('colorSelector.imageExtractor.title_ai')
199
- : translate('colorSelector.imageExtractor.title_local')}
200
- </Typography>
201
-
202
- <Divider sx={{ mb: 3 }} />
203
-
204
- {/* Image Upload Section */}
205
- <Box sx={{ mb: 3 }}>
206
- {!imageUrl ? (
207
- <Box
208
- onDragEnter={handleDragOver}
209
- onDragOver={handleDragOver}
210
- onDragLeave={handleDragLeave}
211
- onDrop={handleDrop}
212
- sx={{
213
- textAlign: 'center',
214
- py: 4,
215
- backgroundColor: isDragOver ? 'rgba(25, 118, 210, 0.08)' : 'rgba(0,0,0,0.02)',
216
- borderRadius: 1,
217
- border: isDragOver ? '2px dashed #1976d2' : '2px dashed #e0e0e0',
218
- transition: 'background-color 0.2s ease, border-color 0.2s ease',
219
- }}
220
- >
221
- <Input
222
- type="file"
223
- inputProps={{ accept: 'image/*' }}
224
- onChange={handleFileSelect}
225
- sx={{ display: 'none' }}
226
- id="color-extractor-upload"
227
- />
228
- <Typography variant="body1" color="textSecondary" gutterBottom>
229
- {translate('colorSelector.imageExtractor.upload_prompt')}
230
- </Typography>
231
- <Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
232
- {translate('colorSelector.imageExtractor.upload_hint')}
233
- </Typography>
234
- <label htmlFor="color-extractor-upload">
235
- <Button
236
- variant="contained"
237
- component="span"
238
- startIcon={<CloudUploadIcon />}
239
- size="large"
240
- >
241
- {translate('colorSelector.imageExtractor.upload_button')}
242
- </Button>
243
- </label>
244
- <Typography variant="caption" color="textSecondary" sx={{ display: 'block', mt: 1.5 }}>
245
- {translate('colorSelector.imageExtractor.drop_hint')}
246
- </Typography>
247
- </Box>
248
- ) : (
249
- <Card sx={{ maxWidth: 400, mb: 3 }}>
250
- <Box sx={{ position: 'relative' }}>
251
- <CardMedia
252
- component="img"
253
- height="200"
254
- image={imageUrl}
255
- alt={translate('colorSelector.imageExtractor.uploaded_image_alt')}
256
- sx={{ objectFit: 'contain' }}
257
- />
258
- <IconButton
259
- sx={{
260
- position: 'absolute',
261
- top: 8,
262
- right: 8,
263
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
264
- '&:hover': {
265
- backgroundColor: 'rgba(255, 255, 255, 1)',
266
- },
267
- }}
268
- onClick={handleImageRemove}
269
- size="small"
270
- >
271
- <DeleteIcon />
272
- </IconButton>
273
- </Box>
274
- </Card>
275
- )}
276
- </Box>
277
-
278
- {/* Color Extraction Status */}
279
- {loading && (
280
- <Box
281
- sx={{
282
- display: "flex",
283
- alignItems: "center",
284
- gap: 2,
285
- mb: 3,
286
- p: 2,
287
- backgroundColor: 'rgba(25, 118, 210, 0.04)',
288
- borderRadius: 1
289
- }}>
290
- <CircularProgress size={20} />
291
- <Typography>{translate('colorSelector.imageExtractor.extracting')}</Typography>
292
- </Box>
293
- )}
294
-
295
- {error && (
296
- <Alert severity="error" sx={{ mb: 3 }}>
297
- {error}
298
- </Alert>
299
- )}
300
-
301
- {themeError && (
302
- <Alert severity="error" sx={{ mb: 3 }}>
303
- {themeError}
304
- </Alert>
305
- )}
306
-
307
- {/* Extracted Colors Display - Use internal state */}
308
- {extractedPalette && extractedPalette.length > 0 && !loading && (
309
- <Box sx={{ mb: 3 }}>
310
- <Typography variant="subtitle1" gutterBottom>
311
- {translate('colorSelector.imageExtractor.extracted_colors', { count: extractedPalette.length })}
312
- </Typography>
313
-
314
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2, mb: 3 }}>
315
- {extractedPalette.map((color, index) => {
316
- const hexColor = rgbArrayToHex(color);
317
- const isDominant = dominantColor &&
318
- color[0] === dominantColor[0] &&
319
- color[1] === dominantColor[1] &&
320
- color[2] === dominantColor[2];
321
-
322
- return (
323
- <Box
324
- key={index}
325
- sx={{
326
- width: 50,
327
- height: 50,
328
- backgroundColor: hexColor,
329
- borderRadius: '50%',
330
- display: 'flex',
331
- alignItems: 'center',
332
- justifyContent: 'center',
333
- border: isDominant ? '3px solid #4caf50' : '2px solid #ccc',
334
- cursor: 'pointer',
335
- transition: 'transform 0.2s',
336
- '&:hover': {
337
- transform: 'scale(1.1)',
338
- },
339
- }}
340
- title={`${hexColor} ${isDominant ? `(${translate('colorSelector.imageExtractor.dominant_label')})` : ''}`}
341
- >
342
- <Typography
343
- variant="caption"
344
- sx={{
345
- color: getContrastColor(hexColor),
346
- fontWeight: 'bold',
347
- fontSize: '0.8rem',
348
- }}
349
- >
350
- {index + 1}
351
- </Typography>
352
- </Box>
353
- );
354
- })}
355
- </Box>
356
-
357
- {/* Theme Generation Controls */}
358
- <Box sx={{ mb: 3 }}>
359
- {aiEnabled && (
360
- <TextField
361
- fullWidth
362
- multiline
363
- rows={2}
364
- placeholder={translate('colorSelector.imageExtractor.theme_description_placeholder')}
365
- value={themePrompt}
366
- onChange={(e) => setThemePrompt(e.target.value)}
367
- sx={{ mb: 2 }}
368
- label={translate('colorSelector.imageExtractor.theme_description_label')}
369
- />
370
- )}
371
-
372
- <Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
373
- {onApplyLocal && (
374
- <Button
375
- variant={aiEnabled ? 'outlined' : 'contained'}
376
- startIcon={<UpdateIcon />}
377
- onClick={() => onApplyLocal(extractedPalette)}
378
- color="primary"
379
- >
380
- {translate('colorSelector.imageExtractor.apply_colors_button')}
381
- </Button>
382
- )}
383
- {aiEnabled && (
384
- <Button
385
- variant="contained"
386
- startIcon={isGeneratingTheme ? <CircularProgress size={16} /> : <AutoFixHighIcon />}
387
- onClick={handleGenerateTheme}
388
- disabled={isGeneratingTheme}
389
- color="primary"
390
- >
391
- {isGeneratingTheme
392
- ? translate('colorSelector.imageExtractor.generating_button')
393
- : translate('colorSelector.imageExtractor.generate_button')}
394
- </Button>
395
- )}
396
- </Box>
397
- </Box>
398
-
399
- <Typography variant="body2" color="textSecondary" sx={{ fontStyle: 'italic' }}>
400
- {onApplyLocal && (
401
- <>
402
- • <strong>{translate('colorSelector.imageExtractor.apply_colors_help_label')}</strong>{' '}
403
- {translate('colorSelector.imageExtractor.apply_colors_help_desc')}
404
- <br />
405
- </>
406
- )}
407
- {aiEnabled && (
408
- <>
409
- • <strong>{translate('colorSelector.imageExtractor.generate_help_label')}</strong>{' '}
410
- {translate('colorSelector.imageExtractor.generate_help_desc')}
411
- </>
412
- )}
413
- </Typography>
414
- </Box>
415
- )}
416
- </Box>
417
- );
418
- });
419
-
420
- export default ImageColorExtractor;
@@ -1,123 +0,0 @@
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
-