@dashadmin/dash-components 1.3.73 → 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.
- package/package.json +24 -21
- package/src/components/AuditLog/AuditLog.tsx +0 -490
- package/src/components/AuditLog/index.ts +0 -2
- package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +0 -917
- package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +0 -39
- package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +0 -916
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -42
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +0 -44
- package/src/components/Json/Json.tsx +0 -312
- package/src/components/JsonColorSelector/JsonColorSelector.tsx +0 -1443
- package/src/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -746
- package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +0 -1135
- package/src/components/JsonColorSelector/JsonCssVarValues.tsx +0 -867
- package/src/components/JsonColorSelector/components/BasicColorSelector.tsx +0 -333
- package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +0 -495
- package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +0 -80
- package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +0 -420
- package/src/components/JsonColorSelector/helpers/functions.tsx +0 -123
- package/src/components/JsonColorSelector/helpers/paletteDerivation.ts +0 -367
- package/src/components/JsonColorSelector/interfaces/interfaces.tsx +0 -22
- package/src/components/JsonColorSelector/useAsyncColorThief.tsx +0 -97
- package/src/components/ListActive/ListActive.tsx +0 -106
- package/src/components/ListActive/ListBoolean.tsx +0 -96
- package/src/components/NotificationPreferences/NotificationPreferences.tsx +0 -480
- package/src/components/NotificationPreferences/index.tsx +0 -2
- package/src/components/SearchableSelects/RASearchableSelect.tsx +0 -303
- package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +0 -617
- package/src/components/SearchableSelects/RASearchableSelectChips.tsx +0 -561
- package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +0 -366
- package/src/components/SearchableSelects/SearchableSelect.tsx +0 -323
- package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +0 -358
- package/src/components/SortableDataGrid/README.md +0 -1
- package/src/components/SortableDataGrid/SortableDataGrid.tsx +0 -56
- package/src/components/SortableDataGrid/SortableDataGridBody.tsx +0 -175
- package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +0 -171
- package/src/components/SortableDataGrid/SortableDataGridRow.tsx +0 -247
- package/src/components/Upload/SingleImageUploader.tsx +0 -55
- package/src/components/custom/PackageCopyMethod.tsx +0 -109
- package/src/components/custom/PackageRadioButtonGroup.tsx +0 -49
- package/src/components/dialog/Basic.tsx +0 -11
- package/src/components/dialog/Correct.tsx +0 -16
- package/src/components/dialog/Error.tsx +0 -15
- package/src/components/dialog/Info.tsx +0 -15
- package/src/components/dialog/InfoV2.tsx +0 -35
- package/src/components/dialog/NotFound.tsx +0 -18
- package/src/components/dialog/QuickSearch.tsx +0 -115
- package/src/components/notifications/NotificationsCenter.tsx +0 -743
- package/src/components/notifications/index.ts +0 -42
- package/src/components/tables/PaginationComponent.tsx +0 -17
- package/src/components/theme/AppLoadingFallback.tsx +0 -42
- package/src/components/theme/ErrorBoundary.tsx +0 -57
- package/src/components/theme/InitialLoader.tsx +0 -16
- package/src/components/theme/NotFound.tsx +0 -124
- package/src/components/theme/NotResults.tsx +0 -56
- package/src/hooks/useDraggable.ts +0 -11
- package/src/hooks/useNotifications.ts +0 -14
- package/src/hooks/useQuickSearch.tsx +0 -29
- package/src/index.tsx +0 -77
- package/src/styles/index.less +0 -0
- package/src/utils/setNativeValue.tsx +0 -23
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Palette derivation — computes the full DASH theme palette from 5 base colors
|
|
3
|
-
* per mode, entirely in the frontend (no AI required).
|
|
4
|
-
*
|
|
5
|
-
* Design-system bases (per mode, from the DASH Design System):
|
|
6
|
-
* --primary-color brand color (sidebar, table headers, links)
|
|
7
|
-
* --primary-contrast companion shade of primary (darker variant, NOT text color)
|
|
8
|
-
* --secondary-color action/CTA color (buttons)
|
|
9
|
-
* --secondary-contrast text color on secondary (buttons text)
|
|
10
|
-
* --highlight-color accent color (badges, highlights)
|
|
11
|
-
*
|
|
12
|
-
* Derivation rules were reverse-engineered from config/tenants.php theme_colors
|
|
13
|
-
* defaults and the kitchntabs app dash-variables.less overrides:
|
|
14
|
-
* - Brand families (sidebar, buttons, links, table headers, scrollbars) inherit
|
|
15
|
-
* from the 5 bases via lighten/darken/alpha transforms.
|
|
16
|
-
* - Greyscale neutrals (module-bg, component-bg, framed_layout-bg, text-*, ...)
|
|
17
|
-
* and semantic alerts (alert-*) are intentionally NOT derived — they keep
|
|
18
|
-
* their current/template values so backgrounds stay greyscale and alert
|
|
19
|
-
* semantics (red=error, green=success...) are never lost.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export type ThemeMode = 'light' | 'dark';
|
|
23
|
-
|
|
24
|
-
export interface BasicPalette {
|
|
25
|
-
primaryColor: string;
|
|
26
|
-
primaryContrast: string;
|
|
27
|
-
secondaryColor: string;
|
|
28
|
-
secondaryContrast: string;
|
|
29
|
-
highlightColor: string;
|
|
30
|
-
// Page/surface background pair — NOT derived from the 5 brand bases above (they
|
|
31
|
-
// stay neutral so content stays readable); set and extracted as plain pass-through
|
|
32
|
-
// values. Real usage today: MUI DataGrid row striping (bodybg-primary = even rows,
|
|
33
|
-
// bodybg-secondary = odd rows) — see dash-styles/src/index.tsx.
|
|
34
|
-
bodyBgPrimary: string;
|
|
35
|
-
bodyBgSecondary: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const BASIC_COLOR_DEFS: { key: keyof BasicPalette; cssBase: string; label: string; description: string }[] = [
|
|
39
|
-
{ key: 'primaryColor', cssBase: 'primary-color', label: 'Primary', description: 'Brand color: sidebar, headers, links' },
|
|
40
|
-
{ key: 'primaryContrast', cssBase: 'primary-contrast', label: 'Primary Contrast', description: 'Darker companion of primary' },
|
|
41
|
-
{ key: 'secondaryColor', cssBase: 'secondary-color', label: 'Secondary', description: 'Action color: buttons, CTAs' },
|
|
42
|
-
{ key: 'secondaryContrast', cssBase: 'secondary-contrast', label: 'Secondary Contrast', description: 'Text over secondary' },
|
|
43
|
-
{ key: 'highlightColor', cssBase: 'highlight-color', label: 'Highlight', description: 'Accent: badges, highlights' },
|
|
44
|
-
{ key: 'bodyBgPrimary', cssBase: 'bodybg-primary', label: 'Body Background', description: 'Page surface (e.g. table row background)' },
|
|
45
|
-
{ key: 'bodyBgSecondary', cssBase: 'bodybg-secondary', label: 'Body Background 2', description: 'Page surface, alternate (e.g. striped rows)' },
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Color math
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
interface RGBA { r: number; g: number; b: number; a: number }
|
|
53
|
-
|
|
54
|
-
const clamp = (v: number, min = 0, max = 255) => Math.min(max, Math.max(min, v));
|
|
55
|
-
|
|
56
|
-
export const parseColor = (input: string): RGBA | null => {
|
|
57
|
-
if (!input) return null;
|
|
58
|
-
const value = input.trim();
|
|
59
|
-
|
|
60
|
-
if (value.startsWith('#')) {
|
|
61
|
-
let hex = value.slice(1);
|
|
62
|
-
if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
|
|
63
|
-
if (hex.length === 6 || hex.length === 8) {
|
|
64
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
65
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
66
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
67
|
-
const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1;
|
|
68
|
-
if ([r, g, b].some(isNaN)) return null;
|
|
69
|
-
return { r, g, b, a };
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const rgbMatch = value.match(/rgba?\(([^)]+)\)/i);
|
|
75
|
-
if (rgbMatch) {
|
|
76
|
-
const parts = rgbMatch[1].split(',').map(p => parseFloat(p.trim()));
|
|
77
|
-
if (parts.length >= 3 && parts.slice(0, 3).every(n => !isNaN(n))) {
|
|
78
|
-
return { r: clamp(parts[0]), g: clamp(parts[1]), b: clamp(parts[2]), a: parts[3] !== undefined && !isNaN(parts[3]) ? parts[3] : 1 };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export const toHex = ({ r, g, b }: RGBA): string =>
|
|
85
|
-
`#${[r, g, b].map(v => Math.round(clamp(v)).toString(16).padStart(2, '0')).join('')}`;
|
|
86
|
-
|
|
87
|
-
/** Mix `color` toward `target` by weight 0..1 (0 = color, 1 = target). */
|
|
88
|
-
const mixRgb = (color: RGBA, target: RGBA, weight: number): RGBA => ({
|
|
89
|
-
r: color.r + (target.r - color.r) * weight,
|
|
90
|
-
g: color.g + (target.g - color.g) * weight,
|
|
91
|
-
b: color.b + (target.b - color.b) * weight,
|
|
92
|
-
a: color.a,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const WHITE: RGBA = { r: 255, g: 255, b: 255, a: 1 };
|
|
96
|
-
const BLACK: RGBA = { r: 0, g: 0, b: 0, a: 1 };
|
|
97
|
-
|
|
98
|
-
/** Lighten a color by mixing toward white. amount 0..1 */
|
|
99
|
-
export const lighten = (color: string, amount: number): string => {
|
|
100
|
-
const rgba = parseColor(color);
|
|
101
|
-
if (!rgba) return color;
|
|
102
|
-
return toHex(mixRgb(rgba, WHITE, amount));
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/** Darken a color by mixing toward black. amount 0..1 */
|
|
106
|
-
export const darken = (color: string, amount: number): string => {
|
|
107
|
-
const rgba = parseColor(color);
|
|
108
|
-
if (!rgba) return color;
|
|
109
|
-
return toHex(mixRgb(rgba, BLACK, amount));
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/** rgba() string of a color with the given alpha. */
|
|
113
|
-
export const withAlpha = (color: string, alphaValue: number): string => {
|
|
114
|
-
const rgba = parseColor(color);
|
|
115
|
-
if (!rgba) return color;
|
|
116
|
-
return `rgba(${Math.round(rgba.r)}, ${Math.round(rgba.g)}, ${Math.round(rgba.b)}, ${alphaValue})`;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export const relativeLuminance = (color: string): number => {
|
|
120
|
-
const rgba = parseColor(color);
|
|
121
|
-
if (!rgba) return 0;
|
|
122
|
-
return (0.299 * rgba.r + 0.587 * rgba.g + 0.114 * rgba.b) / 255;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/** '#ffffff' or '#000000' depending on which reads better over the color. */
|
|
126
|
-
export const contrastText = (color: string): string =>
|
|
127
|
-
relativeLuminance(color) > 0.55 ? '#000000' : '#ffffff';
|
|
128
|
-
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
// HSL helpers (for image → bases mapping)
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
|
|
133
|
-
const rgbToHsl = ({ r, g, b }: RGBA): { h: number; s: number; l: number } => {
|
|
134
|
-
const rn = r / 255, gn = g / 255, bn = b / 255;
|
|
135
|
-
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
|
136
|
-
const l = (max + min) / 2;
|
|
137
|
-
if (max === min) return { h: 0, s: 0, l };
|
|
138
|
-
const d = max - min;
|
|
139
|
-
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
140
|
-
let h: number;
|
|
141
|
-
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) * 60;
|
|
142
|
-
else if (max === gn) h = ((bn - rn) / d + 2) * 60;
|
|
143
|
-
else h = ((rn - gn) / d + 4) * 60;
|
|
144
|
-
return { h, s, l };
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const hueDistance = (a: number, b: number): number => {
|
|
148
|
-
const d = Math.abs(a - b) % 360;
|
|
149
|
-
return d > 180 ? 360 - d : d;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
// Brand-derived palette
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Compute the brand-derived subset of the theme for one mode.
|
|
158
|
-
* Returns keys WITHOUT the mode suffix. Neutral/semantic keys are deliberately
|
|
159
|
-
* absent — callers must merge this over the existing full palette.
|
|
160
|
-
*/
|
|
161
|
-
export const deriveModePalette = (bases: BasicPalette, mode: ThemeMode): Record<string, string> => {
|
|
162
|
-
const P = bases.primaryColor;
|
|
163
|
-
const PC = bases.primaryContrast;
|
|
164
|
-
const S = bases.secondaryColor;
|
|
165
|
-
const SC = bases.secondaryContrast;
|
|
166
|
-
const H = bases.highlightColor;
|
|
167
|
-
const dark = mode === 'dark';
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
// Core bases
|
|
171
|
-
'primary-color': P,
|
|
172
|
-
'primary-contrast': PC,
|
|
173
|
-
'primary-contrast-color': PC,
|
|
174
|
-
'secondary-color': S,
|
|
175
|
-
'secondary-contrast': SC,
|
|
176
|
-
'highlight-color': H,
|
|
177
|
-
'highlight-color-contrast': contrastText(H),
|
|
178
|
-
|
|
179
|
-
// Page background pair — pass-through, not derived from the brand bases.
|
|
180
|
-
'bodybg-primary': bases.bodyBgPrimary,
|
|
181
|
-
'bodybg-secondary': bases.bodyBgSecondary,
|
|
182
|
-
|
|
183
|
-
// Buttons — action family from secondary
|
|
184
|
-
'btn-bg': S,
|
|
185
|
-
'btn-primary-bg': S,
|
|
186
|
-
'btn-hover-bg': darken(S, 0.14),
|
|
187
|
-
'btn-active-bg': darken(S, 0.24),
|
|
188
|
-
'btn-border-color': darken(S, 0.10),
|
|
189
|
-
'btn-color': SC,
|
|
190
|
-
'btn-primary-color': SC,
|
|
191
|
-
|
|
192
|
-
// Links — primary family
|
|
193
|
-
'link-color': dark ? lighten(P, 0.30) : darken(P, 0.12),
|
|
194
|
-
'link-hover': dark ? lighten(P, 0.45) : lighten(P, 0.18),
|
|
195
|
-
'link-hover-color': dark ? lighten(P, 0.45) : lighten(P, 0.18),
|
|
196
|
-
'link-active': dark ? lighten(P, 0.20) : darken(P, 0.24),
|
|
197
|
-
'link-active-color': dark ? lighten(P, 0.20) : darken(P, 0.24),
|
|
198
|
-
'dash-link-color': dark ? lighten(P, 0.35) : darken(P, 0.16),
|
|
199
|
-
|
|
200
|
-
// Table headers — primary family
|
|
201
|
-
'table-header-bg': dark ? darken(P, 0.24) : P,
|
|
202
|
-
'table-header-color': contrastText(dark ? darken(P, 0.24) : P),
|
|
203
|
-
|
|
204
|
-
// Header badge — highlight family
|
|
205
|
-
'header-badge': H,
|
|
206
|
-
'header-badge-hover': lighten(H, 0.12),
|
|
207
|
-
|
|
208
|
-
// Dash sidebar — primary family
|
|
209
|
-
'dash-sidebar-bg': dark ? darken(P, 0.30) : P,
|
|
210
|
-
'dash-sidebar-bg-primary': dark ? darken(P, 0.30) : P,
|
|
211
|
-
'dash-sidebar-bg-contrast': dark ? lighten(P, 0.08) : PC,
|
|
212
|
-
'dash-sidebar-bg-hover': dark ? lighten(P, 0.08) : PC,
|
|
213
|
-
'dash-sidebar-submenu-bg-contrast': dark ? lighten(P, 0.08) : PC,
|
|
214
|
-
'dash-sidebar-submenu-bg-primary': dark ? 'rgba(18, 18, 18, 0.16)' : lighten(P, 0.85),
|
|
215
|
-
'dash-sidebar-color': dark ? lighten(P, 0.25) : contrastText(P),
|
|
216
|
-
'dash-sidebar-color-primary': dark ? lighten(P, 0.25) : contrastText(P),
|
|
217
|
-
'dash-sidebar-color-active': dark ? '#fafafa' : lighten(P, 0.85),
|
|
218
|
-
'dash-sidebar-color-contrast': dark ? lighten(P, 0.55) : lighten(P, 0.85),
|
|
219
|
-
'dash-sidebar-submenu-color-contrast': dark ? lighten(P, 0.55) : lighten(P, 0.85),
|
|
220
|
-
'dash-sidebar-submenu-color-primary': dark ? '#d4d4d4' : P,
|
|
221
|
-
'dash-sidebar-icon-color': dark ? '#f5f5f5' : P,
|
|
222
|
-
'dash-sidebar-icon-color-primary': dark ? '#f5f5f5' : P,
|
|
223
|
-
'dash-sidebar-icon-color-active': dark ? P : lighten(P, 0.85),
|
|
224
|
-
'dash-sidebar-icon-color-secondary': dark ? lighten(P, 0.10) : lighten(P, 0.85),
|
|
225
|
-
'dash-sidebar-handle-primary': dark ? withAlpha(lighten(P, 0.15), 0.4) : 'rgba(255, 255, 255, 0.2)',
|
|
226
|
-
'dash-sidebar-handle-contrast': dark ? withAlpha(P, 0.6) : 'rgba(255, 255, 255, 0.5)',
|
|
227
|
-
|
|
228
|
-
// Dash surfaces / scrollbars — primary family
|
|
229
|
-
'dash-scroll-thumb-color': dark ? P : lighten(P, 0.30),
|
|
230
|
-
'dash-scroll-track-color': dark ? darken(P, 0.30) : lighten(P, 0.88),
|
|
231
|
-
'dash-main-background': dark ? darken(P, 0.35) : '#ffffff',
|
|
232
|
-
'dash-framed-layout-background': dark ? P : '#ffffff',
|
|
233
|
-
'dash-border-color': dark ? '#000000' : lighten(P, 0.72),
|
|
234
|
-
'body-background': dark ? darken(P, 0.28) : P,
|
|
235
|
-
'component-active-background': dark ? darken(P, 0.28) : '#e6e6e6',
|
|
236
|
-
'component-hover-background': dark ? darken(P, 0.22) : '#f8f8f8',
|
|
237
|
-
'component-border-color': dark ? '#000000' : lighten(P, 0.72),
|
|
238
|
-
'border-color-base': dark ? '#000000' : lighten(P, 0.72),
|
|
239
|
-
'border-color-split': dark ? '#000000' : lighten(P, 0.72),
|
|
240
|
-
'module-background': dark ? darken(P, 0.34) : '#f8f8f8',
|
|
241
|
-
};
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
/** Full `{ 'key--mode': value }` record for the brand-derived keys, both modes. */
|
|
245
|
-
export const deriveBrandPairs = (light: BasicPalette, darkBases: BasicPalette): Record<string, string> => {
|
|
246
|
-
const out: Record<string, string> = {};
|
|
247
|
-
Object.entries(deriveModePalette(light, 'light')).forEach(([k, v]) => { out[`${k}--light`] = v; });
|
|
248
|
-
Object.entries(deriveModePalette(darkBases, 'dark')).forEach(([k, v]) => { out[`${k}--dark`] = v; });
|
|
249
|
-
return out;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
// ---------------------------------------------------------------------------
|
|
253
|
-
// Reading bases from an existing palette
|
|
254
|
-
// ---------------------------------------------------------------------------
|
|
255
|
-
|
|
256
|
-
const FALLBACK_BASES: Record<ThemeMode, BasicPalette> = {
|
|
257
|
-
light: {
|
|
258
|
-
primaryColor: '#41ab5d',
|
|
259
|
-
primaryContrast: '#005a32',
|
|
260
|
-
secondaryColor: '#367800',
|
|
261
|
-
secondaryContrast: '#ffffff',
|
|
262
|
-
highlightColor: '#49a000',
|
|
263
|
-
bodyBgPrimary: '#ffffff',
|
|
264
|
-
bodyBgSecondary: '#ececec',
|
|
265
|
-
},
|
|
266
|
-
dark: {
|
|
267
|
-
primaryColor: '#41ab5d',
|
|
268
|
-
primaryContrast: '#005a32',
|
|
269
|
-
secondaryColor: '#238b45',
|
|
270
|
-
secondaryContrast: '#f7fcf5',
|
|
271
|
-
highlightColor: '#74c476',
|
|
272
|
-
bodyBgPrimary: '#101010',
|
|
273
|
-
bodyBgSecondary: 'rgba(36, 36, 36, 1)',
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
/** Extract the 7 base colors for a mode from a full palette record; falls back to defaults. */
|
|
278
|
-
export const extractBases = (colors: Record<string, string>, mode: ThemeMode): BasicPalette => {
|
|
279
|
-
const get = (base: string, fallback: string) => {
|
|
280
|
-
const v = colors[`${base}--${mode}`];
|
|
281
|
-
return v && parseColor(v) ? v : fallback;
|
|
282
|
-
};
|
|
283
|
-
const fb = FALLBACK_BASES[mode];
|
|
284
|
-
return {
|
|
285
|
-
primaryColor: get('primary-color', fb.primaryColor),
|
|
286
|
-
primaryContrast: get('primary-contrast', fb.primaryContrast),
|
|
287
|
-
secondaryColor: get('secondary-color', fb.secondaryColor),
|
|
288
|
-
secondaryContrast: get('secondary-contrast', fb.secondaryContrast),
|
|
289
|
-
highlightColor: get('highlight-color', fb.highlightColor),
|
|
290
|
-
bodyBgPrimary: get('bodybg-primary', fb.bodyBgPrimary),
|
|
291
|
-
bodyBgSecondary: get('bodybg-secondary', fb.bodyBgSecondary),
|
|
292
|
-
};
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
// Image palette → bases (local, no AI)
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Map a ColorThief palette (RGB triples ordered by dominance) to the 5 base
|
|
301
|
-
* colors for both modes. Pure frontend heuristic:
|
|
302
|
-
* primary = most dominant sufficiently-saturated color
|
|
303
|
-
* secondary = next color with a distinct hue (>40° away)
|
|
304
|
-
* highlight = most vivid remaining color
|
|
305
|
-
* Contrasts are computed (primary-contrast = darker companion,
|
|
306
|
-
* secondary-contrast = readable text color).
|
|
307
|
-
*/
|
|
308
|
-
export const basesFromImagePalette = (palette: number[][]): { light: BasicPalette; dark: BasicPalette } => {
|
|
309
|
-
const candidates = palette
|
|
310
|
-
.map(rgb => {
|
|
311
|
-
const rgba: RGBA = { r: rgb[0], g: rgb[1], b: rgb[2], a: 1 };
|
|
312
|
-
return { hex: toHex(rgba), ...rgbToHsl(rgba) };
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
const saturated = candidates.filter(c => c.s > 0.15 && c.l > 0.08 && c.l < 0.92);
|
|
316
|
-
const pool = saturated.length > 0 ? saturated : candidates;
|
|
317
|
-
|
|
318
|
-
const primary = pool[0];
|
|
319
|
-
const secondary =
|
|
320
|
-
pool.find(c => c !== primary && hueDistance(c.h, primary.h) > 40) ||
|
|
321
|
-
pool.find(c => c !== primary) ||
|
|
322
|
-
primary;
|
|
323
|
-
const vividness = (c: { s: number; l: number }) => c.s * (1 - Math.abs(c.l - 0.5) * 2);
|
|
324
|
-
const highlight =
|
|
325
|
-
[...pool]
|
|
326
|
-
.filter(c => c !== primary && c !== secondary)
|
|
327
|
-
.sort((a, b) => vividness(b) - vividness(a))[0] ||
|
|
328
|
-
secondary;
|
|
329
|
-
|
|
330
|
-
// Normalize for usable contrast: primary shouldn't be near-white/near-black.
|
|
331
|
-
const normalize = (hex: string, minL = 0.18, maxL = 0.72): string => {
|
|
332
|
-
const l = rgbToHsl(parseColor(hex)!).l;
|
|
333
|
-
if (l > maxL) return darken(hex, (l - maxL) / (1 - maxL) * 0.6);
|
|
334
|
-
if (l < minL) return lighten(hex, (minL - l) / minL * 0.6);
|
|
335
|
-
return hex;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
const pLight = normalize(primary.hex);
|
|
339
|
-
const sLight = normalize(secondary.hex);
|
|
340
|
-
|
|
341
|
-
const light: BasicPalette = {
|
|
342
|
-
primaryColor: pLight,
|
|
343
|
-
primaryContrast: darken(pLight, 0.40),
|
|
344
|
-
secondaryColor: sLight,
|
|
345
|
-
secondaryContrast: contrastText(sLight),
|
|
346
|
-
highlightColor: normalize(highlight.hex, 0.25, 0.8),
|
|
347
|
-
// Body background stays neutral even when deriving from an image — a page
|
|
348
|
-
// background tinted to a vivid extracted color reads as garish/low-contrast
|
|
349
|
-
// for content. Left at the template default; fine-tune manually if desired.
|
|
350
|
-
bodyBgPrimary: FALLBACK_BASES.light.bodyBgPrimary,
|
|
351
|
-
bodyBgSecondary: FALLBACK_BASES.light.bodyBgSecondary,
|
|
352
|
-
};
|
|
353
|
-
// Dark mode: same hues, slightly brighter for legibility over dark surfaces.
|
|
354
|
-
const pDark = lighten(pLight, 0.08);
|
|
355
|
-
const sDark = lighten(sLight, 0.12);
|
|
356
|
-
const dark: BasicPalette = {
|
|
357
|
-
primaryColor: pDark,
|
|
358
|
-
primaryContrast: darken(pDark, 0.40),
|
|
359
|
-
secondaryColor: sDark,
|
|
360
|
-
secondaryContrast: contrastText(sDark),
|
|
361
|
-
highlightColor: lighten(light.highlightColor, 0.10),
|
|
362
|
-
bodyBgPrimary: FALLBACK_BASES.dark.bodyBgPrimary,
|
|
363
|
-
bodyBgSecondary: FALLBACK_BASES.dark.bodyBgSecondary,
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
return { light, dark };
|
|
367
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,106 +0,0 @@
|
|
|
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;
|
|
@@ -1,96 +0,0 @@
|
|
|
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
|
-
}: IListActive) => {
|
|
26
|
-
const [ ,setLoading] = useState<boolean>(true);
|
|
27
|
-
const [update, { isLoading: updateLoading }] =
|
|
28
|
-
useUpdate();
|
|
29
|
-
const record: any = useRecordContext();
|
|
30
|
-
const onError = (error: any) => {
|
|
31
|
-
console.error(error);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const onChange = useCallback(
|
|
35
|
-
async (value: boolean) => {
|
|
36
|
-
window.dispatchEvent(
|
|
37
|
-
new MessageEvent('dash-global-loader', { data: true })
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
await update(
|
|
42
|
-
resourceConfig.model + '/change-status/' + record.id,
|
|
43
|
-
{
|
|
44
|
-
id: record.id,
|
|
45
|
-
data: { is_active: value },
|
|
46
|
-
previousData: record,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
onSuccess: () => {
|
|
50
|
-
// Force a refresh of the record context
|
|
51
|
-
record.is_active = value;
|
|
52
|
-
},
|
|
53
|
-
onSettled: (data, error) => {
|
|
54
|
-
if (error) onError(error);
|
|
55
|
-
window.dispatchEvent(
|
|
56
|
-
new MessageEvent('dash-global-loader', { data: false })
|
|
57
|
-
);
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
onError(error);
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
[update, record]
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
setLoading(false);
|
|
70
|
-
}, [record]);
|
|
71
|
-
const is_active = record?.is_active ? true : false;
|
|
72
|
-
const textSwitch = record?.is_active ? disableLabel : enableLabel;
|
|
73
|
-
switch (method) {
|
|
74
|
-
case 'create':
|
|
75
|
-
case 'edit':
|
|
76
|
-
return AttributeToInput(method, resourceConfig, {...attribute,custom:false,type:Boolean})
|
|
77
|
-
case 'view':
|
|
78
|
-
return AttributeToField(method, resourceConfig, {...attribute,custom:false,type:Boolean})
|
|
79
|
-
case 'list':
|
|
80
|
-
return (
|
|
81
|
-
<div>
|
|
82
|
-
<Box>
|
|
83
|
-
<Switch
|
|
84
|
-
|
|
85
|
-
onChange={(e, value) => onChange(value)}
|
|
86
|
-
{...(is_active === true && { defaultChecked: true })}
|
|
87
|
-
/>{is_active ? activeLabel : inactiveLabel}
|
|
88
|
-
</Box>
|
|
89
|
-
</div>
|
|
90
|
-
)
|
|
91
|
-
default:
|
|
92
|
-
return <>{attribute.label}: {is_active ? activeLabel : inactiveLabel}</>;
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export default ListActive;
|