@donotdev/ui 0.0.3 → 0.0.4

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 (32) hide show
  1. package/dist/components/auth/AuthMenu.d.ts.map +1 -1
  2. package/dist/components/auth/AuthMenu.js +4 -4
  3. package/dist/components/common/FeatureCard.d.ts +7 -19
  4. package/dist/components/common/FeatureCard.d.ts.map +1 -1
  5. package/dist/components/common/FeatureCard.js +4 -28
  6. package/dist/components/common/TechBento.d.ts +15 -3
  7. package/dist/components/common/TechBento.d.ts.map +1 -1
  8. package/dist/components/common/TechBento.js +20 -2
  9. package/dist/dndev.css +258 -151
  10. package/dist/index.js +4 -4
  11. package/dist/internal/devtools/DebugTools.js +1 -1
  12. package/dist/internal/devtools/components/ConfigTab.d.ts.map +1 -1
  13. package/dist/internal/devtools/components/ConfigTab.js +8 -6
  14. package/dist/internal/devtools/components/DesignTab.d.ts.map +1 -1
  15. package/dist/internal/devtools/components/DesignTab.js +256 -45
  16. package/dist/internal/devtools/components/StoresTab.d.ts.map +1 -1
  17. package/dist/internal/initializers/BaseStoresInitializer.d.ts.map +1 -1
  18. package/dist/internal/initializers/BaseStoresInitializer.js +11 -1
  19. package/dist/internal/layout/components/AutoMetaTags.d.ts.map +1 -1
  20. package/dist/internal/layout/components/AutoMetaTags.js +6 -1
  21. package/dist/internal/layout/zones/DnDevFooter.js +2 -2
  22. package/dist/styles/index.css +258 -151
  23. package/dist/utils/assetResolver.d.ts.map +1 -1
  24. package/dist/utils/assetResolver.js +22 -11
  25. package/dist/utils/index.d.ts +1 -0
  26. package/dist/utils/index.d.ts.map +1 -1
  27. package/dist/utils/index.js +1 -0
  28. package/dist/utils/tList.d.ts +30 -0
  29. package/dist/utils/tList.d.ts.map +1 -0
  30. package/dist/utils/tList.js +51 -0
  31. package/dist/vite-routing/AppRoutes.d.ts.map +1 -1
  32. package/package.json +9 -9
@@ -119,7 +119,7 @@ const DebugTools = ({ className }) => {
119
119
  ? 'Reset consent (show banner)'
120
120
  : 'Consent banner visible', children: hasConsented
121
121
  ? 'Show Cookie Banner'
122
- : 'Cookie Banner Visible' })] }) }), footer: _jsx(Stack, { align: "center", justify: "center", className: cn('drag-handle', current === 'mobile' && 'dndev-text-destructive', current === 'tablet' && 'dndev-text-primary', current === 'desktop' && 'dndev-text-secondary-foreground', current === 'wide' && 'dndev-text-muted-foreground'), style: {
122
+ : 'Cookie Banner Visible' })] }) }), footer: _jsx(Stack, { align: "center", justify: "center", className: cn('drag-handle', current === 'mobile' && 'dndev-text-destructive', current === 'tablet' && 'dndev-text-primary', current === 'laptop' && 'dndev-text-secondary-foreground', current === 'desktop' && 'dndev-text-muted-foreground'), style: {
123
123
  cursor: 'grab',
124
124
  }, children: _jsxs(Label, { className: "dndev-font-mono dndev-text-lg", children: [current.toUpperCase(), " : ", width, " \u00D7 ", height, "px"] }) }) }) }) }));
125
125
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigTab.d.ts","sourceRoot":"","sources":["../../../../src/internal/devtools/components/ConfigTab.tsx"],"names":[],"mappings":"AAwBA,eAAO,MAAM,SAAS,+CAyErB,CAAC"}
1
+ {"version":3,"file":"ConfigTab.d.ts","sourceRoot":"","sources":["../../../../src/internal/devtools/components/ConfigTab.tsx"],"names":[],"mappings":"AAwBA,eAAO,MAAM,SAAS,+CAqGrB,CAAC"}
@@ -26,29 +26,31 @@ export const ConfigTab = () => {
26
26
  },
27
27
  {
28
28
  label: 'Mode',
29
- value: _jsx(Badge, { variant: config.mode === 'development' ? BADGE_VARIANT.DEFAULT : BADGE_VARIANT.SECONDARY, children: config.mode }),
29
+ value: (_jsx(Badge, { variant: config.mode === 'development'
30
+ ? BADGE_VARIANT.DEFAULT
31
+ : BADGE_VARIANT.SECONDARY, children: config.mode })),
30
32
  },
31
33
  {
32
34
  label: 'Version',
33
- value: _jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.version }),
35
+ value: (_jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.version })),
34
36
  },
35
37
  ];
36
38
  const summaryItems = [
37
39
  config.themes && {
38
40
  label: 'Themes',
39
- value: _jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.themes.discovered?.length || 0 }),
41
+ value: (_jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.themes.discovered?.length || 0 })),
40
42
  },
41
43
  config.routes && {
42
44
  label: 'Routes',
43
- value: _jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.routes.mapping?.length || 0 }),
45
+ value: (_jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.routes.mapping?.length || 0 })),
44
46
  },
45
47
  config.i18n && {
46
48
  label: 'Languages',
47
- value: _jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.i18n.languages?.join(', ') }),
49
+ value: (_jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.i18n.languages?.join(', ') })),
48
50
  },
49
51
  config.features && {
50
52
  label: 'Features',
51
- value: _jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.features.available?.length || 0 }),
53
+ value: (_jsx(Text, { className: "dndev-font-mono dndev-text-sm", children: config.features.available?.length || 0 })),
52
54
  },
53
55
  ].filter(Boolean);
54
56
  const envItems = Object.entries(envVars).map(([key, value]) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"DesignTab.d.ts","sourceRoot":"","sources":["../../../../src/internal/devtools/components/DesignTab.tsx"],"names":[],"mappings":"AAgDA,eAAO,MAAM,SAAS,+CA8XrB,CAAC"}
1
+ {"version":3,"file":"DesignTab.d.ts","sourceRoot":"","sources":["../../../../src/internal/devtools/components/DesignTab.tsx"],"names":[],"mappings":"AA8CA,eAAO,MAAM,SAAS,+CAsuBrB,CAAC"}
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  * @author AMBROISE PARK Consulting
11
11
  */
12
12
  import { Palette, Type } from 'lucide-react';
13
- import { useState } from 'react';
13
+ import { useState, useEffect } from 'react';
14
14
  import { Card, Button, BUTTON_VARIANT, Stack, Label, Badge, BADGE_VARIANT, Alert, ALERT_VARIANT, DescriptionList, Text, ScrollArea, Accordion, CopyToClipboard, toast, } from '@donotdev/components';
15
15
  import { useTheme, useThemeReady } from '@donotdev/core';
16
16
  export const DesignTab = () => {
@@ -18,9 +18,137 @@ export const DesignTab = () => {
18
18
  const availableThemes = useTheme('availableThemes');
19
19
  const isDarkMode = useTheme('isDarkMode');
20
20
  const isReady = useThemeReady();
21
+ // Load cached analysis from localStorage
22
+ const loadCachedAnalysis = () => {
23
+ try {
24
+ const cached = localStorage.getItem('dndev-design-analysis');
25
+ if (cached) {
26
+ const parsed = JSON.parse(cached);
27
+ if (parsed.colorRatio)
28
+ setColorRatio(parsed.colorRatio);
29
+ if (parsed.typography)
30
+ setTypography(parsed.typography);
31
+ if (parsed.fontSizeColors)
32
+ setFontSizeColors(parsed.fontSizeColors);
33
+ }
34
+ }
35
+ catch (e) {
36
+ // Ignore cache errors
37
+ }
38
+ };
39
+ // Save analysis to localStorage
40
+ const saveAnalysis = (colorRatioData, typographyData, fontSizeColorsData) => {
41
+ try {
42
+ localStorage.setItem('dndev-design-analysis', JSON.stringify({
43
+ colorRatio: colorRatioData,
44
+ typography: typographyData,
45
+ fontSizeColors: fontSizeColorsData,
46
+ timestamp: new Date().toISOString(),
47
+ }));
48
+ }
49
+ catch (e) {
50
+ // Ignore save errors
51
+ }
52
+ };
53
+ useEffect(() => {
54
+ loadCachedAnalysis();
55
+ }, []);
21
56
  const [colorRatio, setColorRatio] = useState(null);
22
57
  const [typography, setTypography] = useState(null);
23
58
  const [isAnalyzing, setIsAnalyzing] = useState(false);
59
+ const [highlightedSize, setHighlightedSize] = useState(null);
60
+ const [fontSizeColors, setFontSizeColors] = useState({});
61
+ // Normalize font size for comparison (round to 2 decimals)
62
+ const normalizeFontSize = (size) => {
63
+ return parseFloat(size).toFixed(2) + 'px';
64
+ };
65
+ // Highlight/unhighlight elements by font size
66
+ const toggleHighlight = (size) => {
67
+ // Clear previous highlights
68
+ document.querySelectorAll('[data-dndev-highlight]').forEach((el) => {
69
+ el.style.outline = '';
70
+ el.removeAttribute('data-dndev-highlight');
71
+ });
72
+ if (highlightedSize === size) {
73
+ setHighlightedSize(null);
74
+ return;
75
+ }
76
+ // Highlight elements with matching font size - ONLY leaf nodes
77
+ const elements = document.querySelectorAll('*');
78
+ let count = 0;
79
+ const normalizedSize = normalizeFontSize(size);
80
+ elements.forEach((el) => {
81
+ if (isOverlayElement(el))
82
+ return;
83
+ // Only highlight leaf nodes (same logic as analysis)
84
+ const hasText = el.textContent && el.textContent.trim().length > 0;
85
+ const hasChildElements = el.children.length > 0;
86
+ if (!hasText || hasChildElements)
87
+ return;
88
+ const style = getComputedStyle(el);
89
+ if (normalizeFontSize(style.fontSize) === normalizedSize) {
90
+ el.style.outline = '2px solid var(--primary)';
91
+ el.setAttribute('data-dndev-highlight', 'true');
92
+ count++;
93
+ }
94
+ });
95
+ setHighlightedSize(size);
96
+ toast('info', `Highlighting ${count} elements with ${size}`);
97
+ };
98
+ // Apply color to elements with matching font size
99
+ const applyFontSizeColor = (size, color) => {
100
+ // Clear previous color for this size
101
+ document
102
+ .querySelectorAll(`[data-dndev-font-size-color="${size}"]`)
103
+ .forEach((el) => {
104
+ el.style.color = '';
105
+ el.removeAttribute('data-dndev-font-size-color');
106
+ });
107
+ if (!color) {
108
+ setFontSizeColors((prev) => {
109
+ const next = { ...prev };
110
+ delete next[size];
111
+ return next;
112
+ });
113
+ return;
114
+ }
115
+ // Apply color to all elements with this font size - ONLY leaf nodes
116
+ const elements = document.querySelectorAll('*');
117
+ let count = 0;
118
+ const normalizedSize = normalizeFontSize(size);
119
+ elements.forEach((el) => {
120
+ if (isOverlayElement(el))
121
+ return;
122
+ // Only color leaf nodes (same logic as analysis)
123
+ const hasText = el.textContent && el.textContent.trim().length > 0;
124
+ const hasChildElements = el.children.length > 0;
125
+ if (!hasText || hasChildElements)
126
+ return;
127
+ const style = getComputedStyle(el);
128
+ if (normalizeFontSize(style.fontSize) === normalizedSize) {
129
+ el.style.color = color;
130
+ el.setAttribute('data-dndev-font-size-color', size);
131
+ count++;
132
+ }
133
+ });
134
+ const newColors = { ...fontSizeColors, [size]: color };
135
+ setFontSizeColors(newColors);
136
+ saveAnalysis(colorRatio, typography, newColors);
137
+ };
138
+ // Clear highlights on unmount or when dialog closes
139
+ const clearHighlights = () => {
140
+ document.querySelectorAll('[data-dndev-highlight]').forEach((el) => {
141
+ el.style.outline = '';
142
+ el.removeAttribute('data-dndev-highlight');
143
+ });
144
+ document.querySelectorAll('[data-dndev-font-size-color]').forEach((el) => {
145
+ el.style.color = '';
146
+ el.removeAttribute('data-dndev-font-size-color');
147
+ });
148
+ setHighlightedSize(null);
149
+ setFontSizeColors({});
150
+ saveAnalysis(colorRatio, typography, {});
151
+ };
24
152
  // Get CSS variables from :root
25
153
  const getCSSVariables = () => {
26
154
  const root = document.documentElement;
@@ -66,45 +194,34 @@ export const DesignTab = () => {
66
194
  const analyzeTypography = () => {
67
195
  const elements = document.querySelectorAll('*');
68
196
  const fontSizeMap = new Map();
69
- const fontWeightMap = new Map();
70
- const fontFamilyMap = new Map();
71
197
  let analyzedCount = 0;
72
198
  elements.forEach((el) => {
73
- // Skip overlay/devtools elements
74
199
  if (isOverlayElement(el))
75
200
  return;
201
+ if (el.tagName === 'SCRIPT' ||
202
+ el.tagName === 'STYLE' ||
203
+ el.tagName === 'NOSCRIPT')
204
+ return;
76
205
  const style = getComputedStyle(el);
77
206
  const rect = el.getBoundingClientRect();
78
- // Skip invisible elements
79
207
  if (rect.width === 0 || rect.height === 0)
80
208
  return;
209
+ if (style.display === 'none' || style.visibility === 'hidden')
210
+ return;
211
+ // ONLY leaf nodes with text, no children
212
+ const hasText = el.textContent && el.textContent.trim().length > 0;
213
+ if (!hasText || el.children.length > 0)
214
+ return;
81
215
  analyzedCount++;
82
- // Font size
83
216
  const fontSize = style.fontSize;
84
- fontSizeMap.set(fontSize, (fontSizeMap.get(fontSize) || 0) + 1);
85
- // Font weight
86
- const fontWeight = style.fontWeight;
87
- fontWeightMap.set(fontWeight, (fontWeightMap.get(fontWeight) || 0) + 1);
88
- // Font family (simplified)
89
- const fontFamily = style.fontFamily.split(',')[0]?.trim().replace(/"/g, '') || 'unknown';
90
- fontFamilyMap.set(fontFamily, (fontFamilyMap.get(fontFamily) || 0) + 1);
217
+ const fontSizeRounded = parseFloat(fontSize).toFixed(2) + 'px';
218
+ fontSizeMap.set(fontSizeRounded, (fontSizeMap.get(fontSizeRounded) || 0) + 1);
91
219
  });
92
- // Sort by count descending
93
220
  const fontSizes = Array.from(fontSizeMap.entries())
94
221
  .map(([size, count]) => ({ size, count }))
95
- .sort((a, b) => b.count - a.count)
96
- .slice(0, 10);
97
- const fontWeights = Array.from(fontWeightMap.entries())
98
- .map(([weight, count]) => ({ weight, count }))
99
222
  .sort((a, b) => b.count - a.count);
100
- const fontFamilies = Array.from(fontFamilyMap.entries())
101
- .map(([family, count]) => ({ family, count }))
102
- .sort((a, b) => b.count - a.count)
103
- .slice(0, 5);
104
223
  return {
105
224
  fontSizes,
106
- fontWeights,
107
- fontFamilies,
108
225
  totalElements: analyzedCount,
109
226
  };
110
227
  };
@@ -121,7 +238,10 @@ export const DesignTab = () => {
121
238
  const area = rect.width * rect.height;
122
239
  if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent' && area > 0) {
123
240
  const existing = colorMap.get(bg) || { count: 0, area: 0 };
124
- colorMap.set(bg, { count: existing.count + 1, area: existing.area + area });
241
+ colorMap.set(bg, {
242
+ count: existing.count + 1,
243
+ area: existing.area + area,
244
+ });
125
245
  }
126
246
  });
127
247
  const totalArea = Array.from(colorMap.values()).reduce((sum, v) => sum + v.area, 0);
@@ -134,8 +254,12 @@ export const DesignTab = () => {
134
254
  }))
135
255
  .sort((a, b) => b.percentage - a.percentage);
136
256
  // Simplified categorization based on percentage
137
- const dominant = colors.slice(0, 2).reduce((sum, c) => sum + c.percentage, 0);
138
- const secondary = colors.slice(2, 5).reduce((sum, c) => sum + c.percentage, 0);
257
+ const dominant = colors
258
+ .slice(0, 2)
259
+ .reduce((sum, c) => sum + c.percentage, 0);
260
+ const secondary = colors
261
+ .slice(2, 5)
262
+ .reduce((sum, c) => sum + c.percentage, 0);
139
263
  const accent = colors.slice(5).reduce((sum, c) => sum + c.percentage, 0);
140
264
  return {
141
265
  dominant,
@@ -143,15 +267,22 @@ export const DesignTab = () => {
143
267
  accent,
144
268
  details: colors.slice(0, 15).map((c) => ({
145
269
  ...c,
146
- category: c.percentage > 30 ? 'dominant' : c.percentage > 10 ? 'secondary' : 'accent',
270
+ category: c.percentage > 30
271
+ ? 'dominant'
272
+ : c.percentage > 10
273
+ ? 'secondary'
274
+ : 'accent',
147
275
  })),
148
276
  };
149
277
  };
150
278
  const runAnalysis = () => {
151
279
  setIsAnalyzing(true);
152
280
  try {
153
- setColorRatio(analyzeColorRatio());
154
- setTypography(analyzeTypography());
281
+ const colorRatioData = analyzeColorRatio();
282
+ const typographyData = analyzeTypography();
283
+ setColorRatio(colorRatioData);
284
+ setTypography(typographyData);
285
+ saveAnalysis(colorRatioData, typographyData, fontSizeColors);
155
286
  toast('success', 'Analysis complete');
156
287
  }
157
288
  catch (error) {
@@ -178,18 +309,33 @@ export const DesignTab = () => {
178
309
  }, null, 2);
179
310
  };
180
311
  const copyAnalysis = () => {
181
- navigator.clipboard.writeText(getAnalysisJson()).then(() => {
312
+ navigator.clipboard
313
+ .writeText(getAnalysisJson())
314
+ .then(() => {
182
315
  toast('success', 'Analysis copied to clipboard');
183
- }).catch(() => {
316
+ })
317
+ .catch(() => {
184
318
  toast('error', 'Failed to copy');
185
319
  });
186
320
  };
187
321
  const cssVars = getCSSVariables();
188
322
  const themeItems = [
189
- { label: 'Current', value: _jsx(Text, { className: "dndev-font-mono", children: currentTheme }) },
190
- { label: 'Dark Mode', value: _jsx(Badge, { variant: isDarkMode ? BADGE_VARIANT.DEFAULT : BADGE_VARIANT.SECONDARY, children: isDarkMode ? 'Yes' : 'No' }) },
191
- { label: 'Ready', value: _jsx(Badge, { variant: isReady ? BADGE_VARIANT.DEFAULT : BADGE_VARIANT.DESTRUCTIVE, children: isReady ? 'Yes' : 'No' }) },
192
- { label: 'Available', value: _jsxs(Text, { className: "dndev-font-mono", children: [availableThemes.length, " themes"] }) },
323
+ {
324
+ label: 'Current',
325
+ value: _jsx(Text, { className: "dndev-font-mono", children: currentTheme }),
326
+ },
327
+ {
328
+ label: 'Dark Mode',
329
+ value: (_jsx(Badge, { variant: isDarkMode ? BADGE_VARIANT.DEFAULT : BADGE_VARIANT.SECONDARY, children: isDarkMode ? 'Yes' : 'No' })),
330
+ },
331
+ {
332
+ label: 'Ready',
333
+ value: (_jsx(Badge, { variant: isReady ? BADGE_VARIANT.DEFAULT : BADGE_VARIANT.DESTRUCTIVE, children: isReady ? 'Yes' : 'No' })),
334
+ },
335
+ {
336
+ label: 'Available',
337
+ value: (_jsxs(Text, { className: "dndev-font-mono", children: [availableThemes.length, " themes"] })),
338
+ },
193
339
  ];
194
340
  const isColorCompliant = colorRatio &&
195
341
  colorRatio.dominant >= 50 &&
@@ -198,16 +344,30 @@ export const DesignTab = () => {
198
344
  colorRatio.secondary <= 40 &&
199
345
  colorRatio.accent >= 5 &&
200
346
  colorRatio.accent <= 20;
201
- return (_jsxs(Stack, { gap: "medium", style: { padding: 'var(--gap-md)' }, children: [_jsx(Card, { title: "Theme", subtitle: currentTheme, children: _jsx(DescriptionList, { items: themeItems }) }), _jsx(Card, { title: "CSS Variables", subtitle: `${Object.keys(cssVars).length} variables`, children: _jsx(ScrollArea, { className: "dndev-max-h-48", children: _jsx(Stack, { gap: "tight", children: Object.entries(cssVars).map(([name, value]) => (_jsxs(Stack, { direction: "row", align: "center", justify: "between", gap: "tight", children: [_jsx(Text, { className: "dndev-font-mono dndev-text-xs", children: name }), _jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx("div", { style: {
202
- width: '1rem',
203
- height: '1rem',
204
- backgroundColor: `var(${name})`,
205
- border: '1px solid var(--border)',
206
- borderRadius: 'var(--radius-sm)',
207
- } }), _jsx(Text, { className: "dndev-font-mono dndev-text-xs dndev-text-muted-foreground", children: value })] })] }, name))) }) }) }), _jsxs(Stack, { direction: "row", gap: "tight", children: [_jsx(Button, { onClick: runAnalysis, variant: BUTTON_VARIANT.DEFAULT, icon: Palette, disabled: isAnalyzing, style: { flex: 1 }, children: isAnalyzing ? 'Analyzing...' : 'Analyze Page' }), _jsx(Button, { onClick: copyAnalysis, variant: BUTTON_VARIANT.OUTLINE, disabled: !colorRatio && !typography, children: "Copy JSON" })] }), colorRatio && (_jsx(Card, { title: _jsxs(Stack, { direction: "row", align: "center", justify: "between", className: "dndev-w-full", children: [_jsx("span", { children: "60/30/10 Color Ratio" }), _jsx(Badge, { variant: isColorCompliant ? BADGE_VARIANT.DEFAULT : BADGE_VARIANT.SECONDARY, children: isColorCompliant ? '✓ Balanced' : '⚠ Review' })] }), children: _jsxs(Stack, { gap: "tight", children: [_jsxs(Stack, { direction: "row", justify: "between", children: [_jsx("span", { children: "Dominant (60%)" }), _jsxs("span", { className: colorRatio.dominant >= 50 && colorRatio.dominant <= 70 ? 'dndev-text-primary' : 'dndev-text-destructive', children: [colorRatio.dominant.toFixed(1), "%"] })] }), _jsxs(Stack, { direction: "row", justify: "between", children: [_jsx("span", { children: "Secondary (30%)" }), _jsxs("span", { className: colorRatio.secondary >= 20 && colorRatio.secondary <= 40 ? 'dndev-text-primary' : 'dndev-text-destructive', children: [colorRatio.secondary.toFixed(1), "%"] })] }), _jsxs(Stack, { direction: "row", justify: "between", children: [_jsx("span", { children: "Accent (10%)" }), _jsxs("span", { className: colorRatio.accent >= 5 && colorRatio.accent <= 20 ? 'dndev-text-primary' : 'dndev-text-destructive', children: [colorRatio.accent.toFixed(1), "%"] })] }), colorRatio.details.length > 0 && (_jsx(Accordion, { type: "single", collapsible: true, items: [
347
+ return (_jsxs(Stack, { gap: "medium", style: { padding: 'var(--gap-md)' }, children: [_jsx(Accordion, { type: "single", collapsible: true, items: [
348
+ {
349
+ value: 'theme',
350
+ trigger: _jsx("span", { children: "Theme Info" }),
351
+ content: (_jsxs(Stack, { gap: "medium", children: [_jsx(Card, { title: "Theme", subtitle: currentTheme, children: _jsx(DescriptionList, { items: themeItems }) }), _jsx(Card, { title: "CSS Variables", subtitle: `${Object.keys(cssVars).length} variables`, children: _jsx(ScrollArea, { className: "dndev-max-h-48", children: _jsx(Stack, { gap: "tight", children: Object.entries(cssVars).map(([name, value]) => (_jsxs(Stack, { direction: "row", align: "center", justify: "between", gap: "tight", children: [_jsx(Text, { className: "dndev-font-mono dndev-text-xs", children: name }), _jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx("div", { style: {
352
+ width: '1rem',
353
+ height: '1rem',
354
+ backgroundColor: `var(${name})`,
355
+ border: '1px solid var(--border)',
356
+ borderRadius: 'var(--radius-sm)',
357
+ } }), _jsx(Text, { className: "dndev-font-mono dndev-text-xs dndev-text-muted-foreground", children: value })] })] }, name))) }) }) })] })),
358
+ },
359
+ ] }), _jsxs(Stack, { direction: "row", gap: "tight", children: [_jsx(Button, { onClick: runAnalysis, variant: BUTTON_VARIANT.DEFAULT, icon: Palette, disabled: isAnalyzing, style: { flex: 1 }, children: isAnalyzing ? 'Analyzing...' : 'Analyze Page' }), _jsx(Button, { onClick: copyAnalysis, variant: BUTTON_VARIANT.OUTLINE, disabled: !colorRatio && !typography, children: "Copy JSON" })] }), colorRatio && (_jsx(Card, { content: _jsxs(Stack, { direction: "row", align: "center", justify: "between", className: "dndev-w-full", children: [_jsx("span", { children: "60/30/10 Color Ratio" }), _jsx(Badge, { variant: isColorCompliant
360
+ ? BADGE_VARIANT.DEFAULT
361
+ : BADGE_VARIANT.SECONDARY, children: isColorCompliant ? '✓ Balanced' : '⚠ Review' })] }), children: _jsxs(Stack, { gap: "tight", children: [_jsxs(Stack, { direction: "row", justify: "between", children: [_jsx("span", { children: "Dominant (60%)" }), _jsxs("span", { className: colorRatio.dominant >= 50 && colorRatio.dominant <= 70
362
+ ? 'dndev-text-primary'
363
+ : 'dndev-text-destructive', children: [colorRatio.dominant.toFixed(1), "%"] })] }), _jsxs(Stack, { direction: "row", justify: "between", children: [_jsx("span", { children: "Secondary (30%)" }), _jsxs("span", { className: colorRatio.secondary >= 20 && colorRatio.secondary <= 40
364
+ ? 'dndev-text-primary'
365
+ : 'dndev-text-destructive', children: [colorRatio.secondary.toFixed(1), "%"] })] }), _jsxs(Stack, { direction: "row", justify: "between", children: [_jsx("span", { children: "Accent (10%)" }), _jsxs("span", { className: colorRatio.accent >= 5 && colorRatio.accent <= 20
366
+ ? 'dndev-text-primary'
367
+ : 'dndev-text-destructive', children: [colorRatio.accent.toFixed(1), "%"] })] }), colorRatio.details.length > 0 && (_jsx(Accordion, { type: "single", collapsible: true, items: [
208
368
  {
209
369
  value: 'colors',
210
- trigger: _jsxs("span", { className: "dndev-text-sm", children: ["Top Colors (", colorRatio.details.length, ")"] }),
370
+ trigger: (_jsxs("span", { className: "dndev-text-sm", children: ["Top Colors (", colorRatio.details.length, ")"] })),
211
371
  content: (_jsx(Stack, { gap: "tight", children: colorRatio.details.map((c, i) => (_jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx("div", { style: {
212
372
  width: '1rem',
213
373
  height: '1rem',
@@ -216,5 +376,56 @@ export const DesignTab = () => {
216
376
  flexShrink: 0,
217
377
  } }), _jsxs(Text, { className: "dndev-text-xs dndev-font-mono", style: { flex: 1 }, children: [c.percentage.toFixed(1), "%"] }), _jsx(Badge, { variant: BADGE_VARIANT.SECONDARY, children: c.category })] }, i))) })),
218
378
  },
219
- ] }))] }) })), typography && (_jsx(Card, { title: _jsxs(Stack, { direction: "row", align: "center", justify: "between", className: "dndev-w-full", children: [_jsx("span", { children: "Typography" }), _jsxs(Badge, { variant: BADGE_VARIANT.SECONDARY, children: [typography.totalElements, " elements"] })] }), children: _jsxs(Stack, { gap: "medium", children: [_jsxs("div", { children: [_jsxs(Label, { className: "dndev-text-sm dndev-font-semibold", children: ["Font Sizes (", typography.fontSizes.length, ")"] }), _jsx(Stack, { gap: "tight", className: "dndev-mt-xs", children: typography.fontSizes.map((f, i) => (_jsxs(Stack, { direction: "row", justify: "between", children: [_jsx(Text, { className: "dndev-font-mono dndev-text-xs", children: f.size }), _jsxs(Text, { className: "dndev-text-xs dndev-text-muted-foreground", children: [f.count, "\u00D7"] })] }, i))) })] }), _jsxs("div", { children: [_jsxs(Label, { className: "dndev-text-sm dndev-font-semibold", children: ["Font Weights (", typography.fontWeights.length, ")"] }), _jsx(Stack, { direction: "row", gap: "tight", wrap: "wrap", className: "dndev-mt-xs", children: typography.fontWeights.map((f, i) => (_jsxs(Badge, { variant: BADGE_VARIANT.SECONDARY, children: [f.weight, " (", f.count, "\u00D7)"] }, i))) })] }), _jsxs("div", { children: [_jsxs(Label, { className: "dndev-text-sm dndev-font-semibold", children: ["Font Families (", typography.fontFamilies.length, ")"] }), _jsx(Stack, { gap: "tight", className: "dndev-mt-xs", children: typography.fontFamilies.map((f, i) => (_jsxs(Stack, { direction: "row", justify: "between", children: [_jsx(Text, { className: "dndev-font-mono dndev-text-xs", children: f.family }), _jsxs(Text, { className: "dndev-text-xs dndev-text-muted-foreground", children: [f.count, "\u00D7"] })] }, i))) })] })] }) })), !colorRatio && !typography && (_jsx(Alert, { variant: ALERT_VARIANT.INFO, children: "Click \"Analyze Page\" to check color ratio and typography" }))] }));
379
+ ] }))] }) })), typography && (_jsxs(Card, { content: _jsxs(Stack, { direction: "row", align: "center", justify: "between", className: "dndev-w-full", children: [_jsx("span", { children: "Font Sizes" }), _jsxs(Badge, { variant: BADGE_VARIANT.SECONDARY, children: [typography.totalElements, " elements"] })] }), children: [_jsxs(Stack, { direction: "row", align: "center", justify: "between", children: [_jsxs(Label, { className: "dndev-text-sm dndev-font-semibold", children: [typography.fontSizes.length, " sizes"] }), (highlightedSize || Object.keys(fontSizeColors).length > 0) && (_jsx(Button, { variant: BUTTON_VARIANT.GHOST, onClick: clearHighlights, style: { padding: '0 var(--gap-xs)', height: 'auto' }, children: _jsx(Text, { className: "dndev-text-xs", children: "Clear All" }) }))] }), _jsx(Stack, { gap: "tight", className: "dndev-mt-xs", children: typography.fontSizes.map((f, i) => {
380
+ const isOrphan = f.count < 5;
381
+ const isHighlighted = highlightedSize === f.size;
382
+ const currentColor = fontSizeColors[f.size] || '';
383
+ const findOrphanElements = () => {
384
+ const normalizedSize = normalizeFontSize(f.size);
385
+ const found = Array.from(document.querySelectorAll('*')).filter((el) => {
386
+ if (el.closest('[data-dndev-devtools]') ||
387
+ el.closest('[data-radix-portal]') ||
388
+ el.closest('[role="dialog"]'))
389
+ return false;
390
+ const style = getComputedStyle(el);
391
+ const hasText = el.textContent && el.textContent.trim().length > 0;
392
+ if (!hasText || el.children.length > 0)
393
+ return false;
394
+ return normalizeFontSize(style.fontSize) === normalizedSize;
395
+ });
396
+ found.forEach((el) => {
397
+ console.log(el, el.textContent?.trim().substring(0, 50));
398
+ el.style.outline = '2px solid red';
399
+ });
400
+ console.log(`Found ${found.length} elements with ${f.size}`);
401
+ };
402
+ return (_jsxs(Stack, { direction: "row", justify: "between", align: "center", gap: "tight", style: {
403
+ padding: 'var(--gap-xs)',
404
+ borderRadius: 'var(--radius-sm)',
405
+ backgroundColor: isHighlighted
406
+ ? 'var(--primary)'
407
+ : 'transparent',
408
+ color: isHighlighted
409
+ ? 'var(--primary-foreground)'
410
+ : 'inherit',
411
+ }, children: [_jsxs(Stack, { direction: "row", align: "center", gap: "tight", onClick: () => toggleHighlight(f.size), style: { cursor: 'pointer', flex: 1 }, children: [_jsx(Text, { className: "dndev-font-mono dndev-text-xs", children: f.size }), isOrphan && (_jsx(Badge, { variant: BADGE_VARIANT.DESTRUCTIVE, children: "orphan" }))] }), isOrphan ? (_jsx(Button, { variant: BUTTON_VARIANT.GHOST, onClick: (e) => {
412
+ e.stopPropagation();
413
+ findOrphanElements();
414
+ }, style: {
415
+ padding: '0 var(--gap-xs)',
416
+ height: '1.5rem',
417
+ minWidth: '2rem',
418
+ }, title: `Find ${f.size} elements in console`, children: _jsx(Text, { className: "dndev-text-xs", children: "\uD83D\uDD0D" }) })) : (_jsx("input", { type: "color", value: currentColor || '#000000', onChange: (e) => applyFontSizeColor(f.size, e.target.value), onClick: (e) => e.stopPropagation(), style: {
419
+ width: '2rem',
420
+ height: '1.5rem',
421
+ border: '1px solid var(--border)',
422
+ borderRadius: 'var(--radius-sm)',
423
+ cursor: 'pointer',
424
+ padding: 0,
425
+ }, title: `Color elements with ${f.size}` })), _jsxs(Text, { className: "dndev-text-xs", style: {
426
+ opacity: isHighlighted ? 1 : 0.6,
427
+ minWidth: '2rem',
428
+ textAlign: 'end',
429
+ }, children: [f.count, "\u00D7"] })] }, i));
430
+ }) })] })), !colorRatio && !typography && (_jsx(Alert, { variant: ALERT_VARIANT.INFO, children: "Click \"Analyze Page\" to check color ratio and typography" }))] }));
220
431
  };
@@ -1 +1 @@
1
- {"version":3,"file":"StoresTab.d.ts","sourceRoot":"","sources":["../../../../src/internal/devtools/components/StoresTab.tsx"],"names":[],"mappings":"AAwCA,eAAO,MAAM,SAAS,+CAoGrB,CAAC"}
1
+ {"version":3,"file":"StoresTab.d.ts","sourceRoot":"","sources":["../../../../src/internal/devtools/components/StoresTab.tsx"],"names":[],"mappings":"AAwCA,eAAO,MAAM,SAAS,+CAoHrB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"BaseStoresInitializer.d.ts","sourceRoot":"","sources":["../../../src/internal/initializers/BaseStoresInitializer.tsx"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAa,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AA6DvC;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAyGD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACR,QAAQ,EACR,YAAiB,EACjB,aAAqB,EACrB,aAAa,GACd,EAAE,0BAA0B,kDAqI5B"}
1
+ {"version":3,"file":"BaseStoresInitializer.d.ts","sourceRoot":"","sources":["../../../src/internal/initializers/BaseStoresInitializer.tsx"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAa,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AA6DvC;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAyGD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACR,QAAQ,EACR,YAAiB,EACjB,aAAqB,EACrB,aAAa,GACd,EAAE,0BAA0B,kDAgJ5B"}
@@ -5,7 +5,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
5
5
  * @fileoverview Base stores initializer for framework state management
6
6
  * @description Initializes and coordinates all framework stores with proper error handling and readiness checks
7
7
  *
8
- * @version 0.0.1
8
+ * @version 0.0.2
9
9
  * @since 0.0.1
10
10
  * @author AMBROISE PARK Consulting
11
11
  */
@@ -244,6 +244,16 @@ export function BaseStoresInitializer({ children, handlers, customStores = [], s
244
244
  return;
245
245
  removed = true;
246
246
  removeShellLoader({ fade: true }); // Always fade
247
+ // Focus main so PageUp/PageDown works (CSR/SSR safe)
248
+ if (isClient()) {
249
+ requestAnimationFrame(() => {
250
+ const main = document.querySelector('main');
251
+ if (main) {
252
+ main.setAttribute('tabindex', '-1'); // Focusable but not in tab order
253
+ main.focus();
254
+ }
255
+ });
256
+ }
247
257
  };
248
258
  const checkBothReady = () => {
249
259
  if (!frameworkReady || !routeReady || removed)
@@ -1 +1 @@
1
- {"version":3,"file":"AutoMetaTags.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/components/AutoMetaTags.tsx"],"names":[],"mappings":"AA0DA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAwC3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,aAgN1B,CAAC;AAEF;;;GAGG;AAEH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"AutoMetaTags.d.ts","sourceRoot":"","sources":["../../../../src/internal/layout/components/AutoMetaTags.tsx"],"names":[],"mappings":"AA2DA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAwC3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,aAoN1B,CAAC;AAEF;;;GAGG;AAEH,eAAe,YAAY,CAAC"}
@@ -50,6 +50,7 @@ import { Helmet } from 'react-helmet-async';
50
50
  import { useTranslation, useIsClient } from '@donotdev/core';
51
51
  import { getDndevConfig } from '@donotdev/core';
52
52
  import { useSeoConfig, useAppConfig } from '@donotdev/core';
53
+ import { AssetResolver } from '@donotdev/ui';
53
54
  // Platform-specific hooks via conditional exports
54
55
  import { useLocation } from '@donotdev/ui/routing/hooks';
55
56
  /**
@@ -176,7 +177,11 @@ export const AutoMetaTags = () => {
176
177
  name: siteName,
177
178
  logo: {
178
179
  '@type': 'ImageObject',
179
- url: '/logo.png',
180
+ url: AssetResolver.assetExists('/icon-512x512.png')
181
+ ? '/icon-512x512.png'
182
+ : AssetResolver.assetExists('/icon-192x192.png')
183
+ ? '/icon-192x192.png'
184
+ : '/logo.svg',
180
185
  },
181
186
  },
182
187
  }) })] }));
@@ -34,7 +34,7 @@ import { useLegalLinks, } from '../components/footer/useLegalLinks';
34
34
  */
35
35
  function DnDevFooterComponent({ app = {} }) {
36
36
  const { t } = useTranslation('dndev');
37
- const isDesktopOrWide = useBreakpoint('isDesktopOrWide');
37
+ const isLaptopOrDesktop = useBreakpoint('isLaptopOrDesktop');
38
38
  // Explicit null hides footer
39
39
  if (app?.footer === null) {
40
40
  return null;
@@ -48,7 +48,7 @@ function DnDevFooterComponent({ app = {} }) {
48
48
  ? `© ${new Date().getFullYear()} ${app.name || 'App'}. ${t('footer.legal.allRightsReserved')}`
49
49
  : copyrightConfig;
50
50
  // Desktop/Wide: 2-zone layout [Copyright] | [Links + DoNotDev]
51
- if (isDesktopOrWide) {
51
+ if (isLaptopOrDesktop) {
52
52
  return (_jsx("footer", { role: "contentinfo", className: "footer", children: _jsxs(Stack, { direction: "row", align: "center", justify: "between", gap: "none", children: [showCopyright && (_jsx("div", { className: "dndev-flex dndev-justify-start", children: _jsx("span", { className: "footer-copyright", children: copyrightText }) })), _jsx("div", { className: "dndev-flex dndev-justify-end", children: _jsxs(Stack, { direction: "row", gap: "medium", align: "center", children: [links.map((link) => (_jsx(Link, { path: link.path, children: maybeTranslate(t, link.label) }, link.path))), _jsx(FooterBranding, {})] }) })] }) }));
53
53
  }
54
54
  // Mobile/Tablet: stacked layout