@shohojdhara/atomix 0.5.0 → 0.5.2
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/atomix.config.ts +12 -0
- package/build-tools/webpack-loader.js +5 -4
- package/dist/atomix.css +230 -83
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/webpack-loader.js +5 -4
- package/dist/charts.d.ts +24 -23
- package/dist/charts.js +271 -369
- package/dist/charts.js.map +1 -1
- package/dist/config.d.ts +624 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +3 -2
- package/dist/core.js +342 -382
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +4 -6
- package/dist/forms.js +233 -334
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +11 -2
- package/dist/heavy.js +406 -445
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +109 -65
- package/dist/index.esm.js +654 -748
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +621 -717
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.js +59 -60
- package/dist/layout.js.map +1 -1
- package/dist/theme.js +4 -4
- package/dist/theme.js.map +1 -1
- package/package.json +24 -9
- package/scripts/atomix-cli.js +15 -1
- package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
- package/scripts/cli/__tests__/detector.test.js +50 -0
- package/scripts/cli/__tests__/template-engine.test.js +23 -0
- package/scripts/cli/__tests__/test-setup.js +1 -133
- package/scripts/cli/commands/doctor.js +15 -3
- package/scripts/cli/commands/generate.js +113 -51
- package/scripts/cli/internal/ai-engine.js +30 -10
- package/scripts/cli/internal/complexity-utils.js +60 -0
- package/scripts/cli/internal/component-validator.js +49 -16
- package/scripts/cli/internal/generator.js +89 -36
- package/scripts/cli/internal/hook-generator.js +5 -2
- package/scripts/cli/internal/itcss-generator.js +16 -12
- package/scripts/cli/templates/next-templates.js +81 -30
- package/scripts/cli/templates/storybook-templates.js +12 -2
- package/scripts/cli/utils/detector.js +45 -7
- package/scripts/cli/utils/diagnostics.js +78 -0
- package/scripts/cli/utils/telemetry.js +13 -0
- package/src/components/Accordion/Accordion.stories.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
- package/src/components/AtomixGlass/glass-utils.ts +51 -1
- package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
- package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
- package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
- package/src/components/AtomixGlass/stories/types.ts +3 -3
- package/src/components/Button/Button.tsx +114 -57
- package/src/components/Callout/Callout.tsx +4 -4
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +11 -8
- package/src/components/EdgePanel/EdgePanel.tsx +119 -115
- package/src/components/Form/Select.tsx +4 -4
- package/src/components/List/List.tsx +4 -4
- package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
- package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +4 -2
- package/src/components/Rating/Rating.tsx +4 -2
- package/src/components/SectionIntro/SectionIntro.tsx +4 -2
- package/src/components/Steps/Steps.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +5 -5
- package/src/components/Testimonial/Testimonial.tsx +4 -2
- package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
- package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
- package/src/layouts/CssGrid/CssGrid.tsx +215 -0
- package/src/layouts/CssGrid/index.ts +8 -0
- package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
- package/src/layouts/CssGrid/scripts/index.js +43 -0
- package/src/layouts/Grid/scripts/Container.js +139 -0
- package/src/layouts/Grid/scripts/Grid.js +184 -0
- package/src/layouts/Grid/scripts/GridCol.js +273 -0
- package/src/layouts/Grid/scripts/Row.js +154 -0
- package/src/layouts/Grid/scripts/index.js +48 -0
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
- package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
- package/src/lib/composables/useAccordion.ts +5 -5
- package/src/lib/composables/useAtomixGlass.ts +111 -74
- package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
- package/src/lib/composables/useBarChart.ts +2 -2
- package/src/lib/composables/useChart.ts +3 -2
- package/src/lib/composables/useChartToolbar.ts +48 -66
- package/src/lib/composables/useDataTable.ts +1 -1
- package/src/lib/composables/useDatePicker.ts +2 -2
- package/src/lib/composables/useEdgePanel.ts +45 -54
- package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
- package/src/lib/composables/usePhotoViewer.ts +2 -3
- package/src/lib/composables/usePieChart.ts +1 -1
- package/src/lib/composables/usePopover.ts +151 -139
- package/src/lib/composables/useSideMenu.ts +28 -41
- package/src/lib/composables/useSlider.ts +2 -6
- package/src/lib/composables/useTooltip.ts +2 -2
- package/src/lib/config/index.ts +39 -0
- package/src/lib/constants/components.ts +1 -0
- package/src/lib/theme/devtools/Comparator.tsx +1 -1
- package/src/lib/theme/devtools/Inspector.tsx +1 -1
- package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
- package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
- package/src/lib/types/components.ts +1 -0
- package/src/styles/01-settings/_index.scss +1 -0
- package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
- package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
- package/src/styles/02-tools/_tools.glass.scss +6 -0
- package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
- package/src/styles/06-components/_components.atomix-glass.scss +160 -99
- package/scripts/cli/__tests__/README.md +0 -81
- package/scripts/cli/__tests__/basic.test.js +0 -116
- package/scripts/cli/__tests__/clean.test.js +0 -278
- package/scripts/cli/__tests__/component-generator.test.js +0 -332
- package/scripts/cli/__tests__/component-validator.test.js +0 -433
- package/scripts/cli/__tests__/generator.test.js +0 -613
- package/scripts/cli/__tests__/glass-motion.test.js +0 -256
- package/scripts/cli/__tests__/integration.test.js +0 -938
- package/scripts/cli/__tests__/migrate.test.js +0 -74
- package/scripts/cli/__tests__/security.test.js +0 -206
- package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
- package/scripts/cli/__tests__/token-manager.test.js +0 -251
- package/scripts/cli/__tests__/token-provider.test.js +0 -361
- package/scripts/cli/__tests__/utils.test.js +0 -165
- package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
- package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
- package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
- package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
- package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
- package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
- package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
- package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
- package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
- package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
- package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
- package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
- package/src/components/TypedButton/TypedButton.tsx +0 -39
- package/src/components/TypedButton/index.ts +0 -2
- package/src/lib/composables/useBreadcrumb.ts +0 -81
- package/src/lib/composables/useChartInteractions.ts +0 -123
- package/src/lib/composables/useChartPerformance.ts +0 -347
- package/src/lib/composables/useDropdown.ts +0 -338
- package/src/lib/composables/useModal.ts +0 -110
- package/src/lib/composables/useTypedButton.ts +0 -66
- package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
- package/src/lib/utils/displacement-generator.ts +0 -92
- package/src/lib/utils/memoryMonitor.ts +0 -191
- package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
- package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
- package/src/styles/06-components/_components.testbutton.scss +0 -212
- package/src/styles/06-components/_components.testtypecheck.scss +0 -212
- package/src/styles/06-components/_components.typedbutton.scss +0 -212
|
@@ -1,63 +1,50 @@
|
|
|
1
|
-
import React, { forwardRef, useRef, useState, useEffect, useMemo } from 'react';
|
|
1
|
+
import React, { forwardRef, useId, useRef, useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import type { CSSProperties } from 'react';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
DisplacementMode,
|
|
5
|
+
MousePosition,
|
|
6
|
+
GlassSize,
|
|
7
|
+
AtomixGlassProps,
|
|
8
|
+
} from '../../lib/types/components';
|
|
9
|
+
import type { FragmentShaderType, ShaderOptions, Vec2 } from './shader-utils';
|
|
5
10
|
import { GlassFilter } from './GlassFilter';
|
|
6
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
calculateMouseInfluence,
|
|
13
|
+
clampBlur,
|
|
14
|
+
validateGlassSize,
|
|
15
|
+
getCachedShader,
|
|
16
|
+
setCachedShader,
|
|
17
|
+
} from './glass-utils';
|
|
7
18
|
import { ATOMIX_GLASS } from '../../lib/constants/components';
|
|
8
19
|
|
|
9
20
|
const { CONSTANTS } = ATOMIX_GLASS;
|
|
10
21
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
// ─── Blur multiplier constants (module-level, never change at runtime) ────────
|
|
23
|
+
const EDGE_BLUR_MULTIPLIER = 1.25;
|
|
24
|
+
const CENTER_BLUR_MULTIPLIER = 1.1;
|
|
25
|
+
const FLOW_BLUR_MULTIPLIER = 1.2;
|
|
26
|
+
const MOUSE_INFLUENCE_BLUR_FACTOR = 0.15;
|
|
27
|
+
const EDGE_INTENSITY_MULTIPLIER = 1.5;
|
|
28
|
+
const EDGE_INTENSITY_MOUSE_FACTOR = 0.15;
|
|
29
|
+
const CENTER_INTENSITY_DISTANCE_FACTOR = 0.3;
|
|
30
|
+
const CENTER_INTENSITY_MOUSE_FACTOR = 0.1;
|
|
31
|
+
/** Maximum blur multiplier relative to base — prevents runaway blur. */
|
|
32
|
+
const MAX_BLUR_RELATIVE = 2;
|
|
33
|
+
|
|
34
|
+
// ─── Shader utility types ─────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
interface ShaderGenerator {
|
|
37
|
+
updateShader(): string;
|
|
38
|
+
destroy(): void;
|
|
19
39
|
}
|
|
20
|
-
const sharedShaderCache = new Map<string, ShaderCacheEntry>();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get cached shader URL, updating access timestamp
|
|
24
|
-
*/
|
|
25
|
-
const getCachedShader = (key: string): string | null => {
|
|
26
|
-
const entry = sharedShaderCache.get(key);
|
|
27
|
-
if (entry) {
|
|
28
|
-
// Update access timestamp for LRU
|
|
29
|
-
entry.timestamp = Date.now();
|
|
30
|
-
return entry.url;
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
};
|
|
34
40
|
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
const setCachedShader = (key: string, url: string): void => {
|
|
39
|
-
// Evict oldest entries if at capacity
|
|
40
|
-
if (sharedShaderCache.size >= MAX_CACHE_SIZE) {
|
|
41
|
-
const entries = Array.from(sharedShaderCache.entries());
|
|
42
|
-
// Sort by timestamp (oldest first)
|
|
43
|
-
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
44
|
-
// Remove oldest entry
|
|
45
|
-
const oldestEntry = entries[0];
|
|
46
|
-
if (oldestEntry) {
|
|
47
|
-
sharedShaderCache.delete(oldestEntry[0]);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
sharedShaderCache.set(key, { url, timestamp: Date.now() });
|
|
41
|
+
/** Fragment shader function — signature matches shader-utils.ts */
|
|
42
|
+
type FragmentShaderFn = (uv: Vec2, mousePosition?: Vec2) => Vec2;
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
`AtomixGlass: Shader cache size: ${String(sharedShaderCache.size).replace(/[\r\n]/g, '')}/${String(MAX_CACHE_SIZE).replace(/[\r\n]/g, '')}`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
44
|
+
interface ShaderUtilsModule {
|
|
45
|
+
ShaderDisplacementGenerator: new (opts: ShaderOptions) => ShaderGenerator;
|
|
46
|
+
fragmentShaders: Record<string, FragmentShaderFn>;
|
|
47
|
+
}
|
|
61
48
|
|
|
62
49
|
interface AtomixGlassContainerProps
|
|
63
50
|
extends Pick<
|
|
@@ -101,11 +88,12 @@ interface AtomixGlassContainerProps
|
|
|
101
88
|
effectiveReducedMotion?: boolean;
|
|
102
89
|
shaderVariant?: FragmentShaderType;
|
|
103
90
|
withLiquidBlur?: boolean;
|
|
91
|
+
isFixedOrSticky?: boolean;
|
|
104
92
|
|
|
105
93
|
// Phase 1: Animation System props
|
|
106
94
|
shaderTime?: number;
|
|
107
95
|
|
|
108
|
-
contentRef?: React.RefObject<HTMLDivElement>;
|
|
96
|
+
contentRef?: React.RefObject<HTMLDivElement | null>;
|
|
109
97
|
children?: React.ReactNode;
|
|
110
98
|
}
|
|
111
99
|
|
|
@@ -140,6 +128,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
140
128
|
effectiveReducedMotion = false,
|
|
141
129
|
shaderVariant = 'liquidGlass',
|
|
142
130
|
withLiquidBlur = false,
|
|
131
|
+
isFixedOrSticky = false,
|
|
143
132
|
|
|
144
133
|
// Phase 1: Animation System props
|
|
145
134
|
shaderTime,
|
|
@@ -155,23 +144,17 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
155
144
|
},
|
|
156
145
|
ref
|
|
157
146
|
) => {
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
const filterId = useMemo(() => {
|
|
161
|
-
return `atomix-glass-filter-${++idCounter}`;
|
|
162
|
-
}, []);
|
|
147
|
+
// React 18 useId — stable, unique, and SSR-safe (no module-level counter)
|
|
148
|
+
const rawId = useId();
|
|
149
|
+
const filterId = useMemo(() => `atomix-glass-filter-${rawId.replace(/:/g, '')}`, [rawId]);
|
|
163
150
|
|
|
164
151
|
const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
|
|
165
|
-
const shaderGeneratorRef = useRef<
|
|
166
|
-
const shaderUtilsRef = useRef<
|
|
167
|
-
ShaderDisplacementGenerator: any;
|
|
168
|
-
fragmentShaders: any;
|
|
169
|
-
} | null>(null);
|
|
152
|
+
const shaderGeneratorRef = useRef<ShaderGenerator | null>(null);
|
|
153
|
+
const shaderUtilsRef = useRef<ShaderUtilsModule | null>(null);
|
|
170
154
|
|
|
171
|
-
// Use shared module-level cache (no per-instance cache needed)
|
|
172
155
|
const shaderDebounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
173
156
|
const shaderUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
174
|
-
|
|
157
|
+
|
|
175
158
|
// Phase 1: Animation frame ref for continuous shader updates
|
|
176
159
|
const animationFrameRef = useRef<number | null>(null);
|
|
177
160
|
|
|
@@ -228,7 +211,8 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
228
211
|
try {
|
|
229
212
|
const { ShaderDisplacementGenerator, fragmentShaders } = shaderUtilsRef.current;
|
|
230
213
|
shaderGeneratorRef.current?.destroy();
|
|
231
|
-
const selectedShader = fragmentShaders[shaderVariant]
|
|
214
|
+
const selectedShader = (fragmentShaders[shaderVariant] ??
|
|
215
|
+
fragmentShaders.liquidGlass) as FragmentShaderFn;
|
|
232
216
|
shaderGeneratorRef.current = new ShaderDisplacementGenerator({
|
|
233
217
|
width: glassSize.width,
|
|
234
218
|
height: glassSize.height,
|
|
@@ -236,7 +220,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
236
220
|
});
|
|
237
221
|
|
|
238
222
|
shaderUpdateTimeoutRef.current = setTimeout(() => {
|
|
239
|
-
const url = shaderGeneratorRef.current?.updateShader()
|
|
223
|
+
const url = shaderGeneratorRef.current?.updateShader() ?? '';
|
|
240
224
|
if (url) {
|
|
241
225
|
setCachedShader(cacheKey, url);
|
|
242
226
|
}
|
|
@@ -301,16 +285,18 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
301
285
|
? 24
|
|
302
286
|
: 20;
|
|
303
287
|
const effectiveSpeed = Math.max(0.5, Math.min(2, animationSpeed || 1));
|
|
304
|
-
const complexity =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
288
|
+
const complexity = withMultiLayerDistortion
|
|
289
|
+
? Math.max(
|
|
290
|
+
1,
|
|
291
|
+
(distortionOctaves || 3) / 3 +
|
|
292
|
+
Math.max(0, (distortionLacunarity || 2) - 2) * 0.25 +
|
|
293
|
+
Math.max(0, (distortionGain || 0.5) - 0.5)
|
|
294
|
+
)
|
|
295
|
+
: 1;
|
|
296
|
+
const targetFps = Math.max(
|
|
297
|
+
12,
|
|
298
|
+
Math.min(60, Math.round((baseFps * effectiveSpeed) / complexity))
|
|
299
|
+
);
|
|
314
300
|
const frameInterval = 1000 / targetFps;
|
|
315
301
|
let lastUpdate = 0;
|
|
316
302
|
let isCancelled = false;
|
|
@@ -380,18 +366,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
380
366
|
return undefined;
|
|
381
367
|
}, [ref, glassSize]);
|
|
382
368
|
|
|
383
|
-
// Pre-calculate static multipliers outside useMemo
|
|
384
|
-
const EDGE_BLUR_MULTIPLIER = 1.25;
|
|
385
|
-
const CENTER_BLUR_MULTIPLIER = 1.1;
|
|
386
|
-
const FLOW_BLUR_MULTIPLIER = 1.2;
|
|
387
|
-
const MOUSE_INFLUENCE_BLUR_FACTOR = 0.15;
|
|
388
|
-
const EDGE_INTENSITY_MULTIPLIER = 1.5;
|
|
389
|
-
const EDGE_INTENSITY_MOUSE_FACTOR = 0.15;
|
|
390
|
-
const CENTER_INTENSITY_DISTANCE_FACTOR = 0.3;
|
|
391
|
-
const CENTER_INTENSITY_MOUSE_FACTOR = 0.1;
|
|
392
|
-
// Maximum blur multiplier relative to base — prevents runaway blur
|
|
393
|
-
const MAX_BLUR_RELATIVE = 2;
|
|
394
|
-
|
|
395
369
|
const liquidBlur = useMemo(() => {
|
|
396
370
|
const defaultBlur = {
|
|
397
371
|
baseBlur: blurAmount,
|
|
@@ -562,7 +536,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
562
536
|
} as React.CSSProperties;
|
|
563
537
|
}
|
|
564
538
|
}, [
|
|
565
|
-
|
|
539
|
+
borderRadius,
|
|
566
540
|
backdropStyle,
|
|
567
541
|
mouseOffset,
|
|
568
542
|
overLight,
|
|
@@ -573,8 +547,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
573
547
|
return (
|
|
574
548
|
<div
|
|
575
549
|
ref={el => {
|
|
576
|
-
// Apply force no-transition
|
|
577
|
-
|
|
578
550
|
// Handle forwarded ref
|
|
579
551
|
if (typeof ref === 'function') {
|
|
580
552
|
ref(el);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
import type { PerformanceMetrics } from '../../lib/composables/usePerformanceMonitor';
|
|
3
3
|
|
|
4
4
|
interface PerformanceDashboardProps {
|
|
@@ -7,213 +7,165 @@ interface PerformanceDashboardProps {
|
|
|
7
7
|
onClose?: () => void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/** Map an FPS value to a semantic color token string. */
|
|
11
|
+
const getFpsColor = (fps: number): string => {
|
|
12
|
+
if (fps >= 58) return 'var(--atomix-color-success, #4ade80)';
|
|
13
|
+
if (fps >= 45) return 'var(--atomix-color-warning, #fbbf24)';
|
|
14
|
+
return 'var(--atomix-color-danger, #ef4444)';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Map a quality level string to a semantic color token string. */
|
|
18
|
+
const getQualityColor = (quality: string): string => {
|
|
19
|
+
switch (quality) {
|
|
20
|
+
case 'high': return 'var(--atomix-color-success, #4ade80)';
|
|
21
|
+
case 'medium': return 'var(--atomix-color-warning, #fbbf24)';
|
|
22
|
+
case 'low': return 'var(--atomix-color-danger, #ef4444)';
|
|
23
|
+
default: return '#9ca3af';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getFpsLabel = (fps: number): string => {
|
|
28
|
+
if (fps >= 58) return 'Optimal';
|
|
29
|
+
if (fps >= 45) return 'Warning';
|
|
30
|
+
return 'Critical';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Keyframes for pulse animation (injected via style tag)
|
|
34
|
+
const keyframesStyle = `
|
|
35
|
+
@keyframes perf-dashboard-pulse {
|
|
36
|
+
0%, 100% { opacity: 1; }
|
|
37
|
+
50% { opacity: 0.5; }
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
// Inject keyframes once
|
|
42
|
+
if (typeof document !== 'undefined') {
|
|
43
|
+
const styleId = 'perf-dashboard-keyframes';
|
|
44
|
+
if (!document.getElementById(styleId)) {
|
|
45
|
+
const styleEl = document.createElement('style');
|
|
46
|
+
styleEl.id = styleId;
|
|
47
|
+
styleEl.textContent = keyframesStyle;
|
|
48
|
+
document.head.appendChild(styleEl);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
10
52
|
/**
|
|
11
|
-
* PerformanceDashboard - Real-time performance monitoring overlay
|
|
12
|
-
*
|
|
13
|
-
* Displays
|
|
14
|
-
*
|
|
15
|
-
* - Frame time statistics
|
|
16
|
-
* - Quality level indicator
|
|
17
|
-
* - GPU memory usage (if available)
|
|
18
|
-
* - Auto-scaling status
|
|
53
|
+
* PerformanceDashboard - Real-time performance monitoring overlay.
|
|
54
|
+
*
|
|
55
|
+
* Displays FPS, frame time, quality level, GPU memory, and auto-scaling status.
|
|
56
|
+
* Rendered only when `debugPerformance={true}` on the parent `AtomixGlass`.
|
|
19
57
|
*/
|
|
20
|
-
export const PerformanceDashboard: React.FC<PerformanceDashboardProps> = (
|
|
21
|
-
metrics,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
fontSize: '12px',
|
|
53
|
-
color: '#fff',
|
|
54
|
-
zIndex: 9999,
|
|
55
|
-
minWidth: '200px',
|
|
56
|
-
backdropFilter: 'blur(8px)',
|
|
57
|
-
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
58
|
-
transition: 'opacity 0.3s ease',
|
|
59
|
-
opacity: isVisible ? 1 : 0,
|
|
60
|
-
pointerEvents: isVisible ? 'auto' : 'none',
|
|
61
|
-
}), [isVisible]);
|
|
62
|
-
|
|
63
|
-
const headerStyle: React.CSSProperties = useMemo(() => ({
|
|
64
|
-
display: 'flex',
|
|
65
|
-
justifyContent: 'space-between',
|
|
66
|
-
alignItems: 'center',
|
|
67
|
-
marginBottom: '8px',
|
|
68
|
-
paddingBottom: '8px',
|
|
69
|
-
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
|
70
|
-
}), []);
|
|
71
|
-
|
|
72
|
-
const titleStyle: React.CSSProperties = useMemo(() => ({
|
|
73
|
-
fontWeight: 'bold',
|
|
74
|
-
fontSize: '13px',
|
|
75
|
-
color: '#fff',
|
|
76
|
-
}), []);
|
|
77
|
-
|
|
78
|
-
const closeButtonStyle: React.CSSProperties = useMemo(() => ({
|
|
79
|
-
background: 'transparent',
|
|
80
|
-
border: 'none',
|
|
81
|
-
color: '#9ca3af',
|
|
82
|
-
cursor: 'pointer',
|
|
83
|
-
fontSize: '16px',
|
|
84
|
-
padding: '0',
|
|
85
|
-
lineHeight: '1',
|
|
86
|
-
}), []);
|
|
87
|
-
|
|
88
|
-
const metricRowStyle: React.CSSProperties = useMemo(() => ({
|
|
89
|
-
display: 'flex',
|
|
90
|
-
justifyContent: 'space-between',
|
|
91
|
-
alignItems: 'center',
|
|
92
|
-
marginBottom: '6px',
|
|
93
|
-
}), []);
|
|
94
|
-
|
|
95
|
-
const labelStyle: React.CSSProperties = useMemo(() => ({
|
|
96
|
-
color: '#9ca3af',
|
|
97
|
-
marginRight: '12px',
|
|
98
|
-
}), []);
|
|
99
|
-
|
|
100
|
-
const valueStyle: React.CSSProperties = useMemo(() => ({
|
|
101
|
-
fontWeight: 'bold',
|
|
102
|
-
}), []);
|
|
103
|
-
|
|
104
|
-
if (!isVisible) return null;
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<div style={dashboardStyle}>
|
|
108
|
-
{/* Header */}
|
|
109
|
-
<div style={headerStyle}>
|
|
110
|
-
<span style={titleStyle}>Performance Monitor</span>
|
|
111
|
-
{onClose && (
|
|
112
|
-
<button
|
|
113
|
-
style={closeButtonStyle}
|
|
114
|
-
onClick={onClose}
|
|
115
|
-
aria-label="Close performance dashboard"
|
|
116
|
-
>
|
|
117
|
-
×
|
|
118
|
-
</button>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
{/* FPS Display */}
|
|
123
|
-
<div style={metricRowStyle}>
|
|
124
|
-
<span style={labelStyle}>FPS</span>
|
|
125
|
-
<span
|
|
126
|
-
style={{
|
|
127
|
-
...valueStyle,
|
|
128
|
-
color: getFpsColor(metrics.fps)
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
{Math.round(metrics.fps)}
|
|
132
|
-
</span>
|
|
133
|
-
</div>
|
|
58
|
+
export const PerformanceDashboard: React.FC<PerformanceDashboardProps> = memo(
|
|
59
|
+
({ metrics, isVisible = true, onClose }) => {
|
|
60
|
+
if (!isVisible) return null;
|
|
61
|
+
|
|
62
|
+
const fpsColor = getFpsColor(metrics.fps);
|
|
63
|
+
const isCritical = metrics.fps < 45;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
className="c-perf-dashboard u-position-fixed u-top-4 u-end-4 u-p-3 u-px-4 u-text-xs u-font-mono u-text-white u-rounded-md u-border u-border-white-alpha-10 u-shadow-lg"
|
|
68
|
+
style={{
|
|
69
|
+
zIndex: 9999,
|
|
70
|
+
minWidth: '12.5rem', // 200px
|
|
71
|
+
backgroundColor: 'rgba(17, 24, 39, 0.95)',
|
|
72
|
+
backdropFilter: 'blur(8px)',
|
|
73
|
+
transition: 'opacity 0.3s ease',
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{/* Header */}
|
|
77
|
+
<div className="u-flex u-items-center u-justify-between u-mb-2 u-pb-2 u-border-b u-border-white-alpha-10">
|
|
78
|
+
<span className="u-text-sm u-font-bold u-text-white">Performance Monitor</span>
|
|
79
|
+
{onClose && (
|
|
80
|
+
<button
|
|
81
|
+
className="u-bg-transparent u-border-none u-p-0 u-line-height-1 u-text-base u-text-gray-400 u-cursor-pointer hover:u-text-white"
|
|
82
|
+
onClick={onClose}
|
|
83
|
+
aria-label="Close performance dashboard"
|
|
84
|
+
style={{ transition: 'color 0.2s ease' }}
|
|
85
|
+
>
|
|
86
|
+
×
|
|
87
|
+
</button>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
134
90
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
91
|
+
{/* FPS */}
|
|
92
|
+
<div className="u-flex u-items-center u-justify-between u-mb-1-5">
|
|
93
|
+
<span className="u-text-gray-400 u-me-3">FPS</span>
|
|
94
|
+
<span className="u-font-bold" style={{ color: fpsColor }}>
|
|
95
|
+
{Math.round(metrics.fps)}
|
|
96
|
+
</span>
|
|
97
|
+
</div>
|
|
142
98
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
textTransform: 'uppercase',
|
|
151
|
-
fontSize: '11px',
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
{metrics.qualityLevel}
|
|
155
|
-
</span>
|
|
156
|
-
</div>
|
|
99
|
+
{/* Frame Time */}
|
|
100
|
+
<div className="u-flex u-items-center u-justify-between u-mb-1-5">
|
|
101
|
+
<span className="u-text-gray-400 u-me-3">Frame Time</span>
|
|
102
|
+
<span className="u-font-bold">
|
|
103
|
+
{metrics.frameTime.toFixed(2)}ms
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
157
106
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<span
|
|
162
|
-
|
|
163
|
-
|
|
107
|
+
{/* Quality Level */}
|
|
108
|
+
<div className="u-flex u-items-center u-justify-between u-mb-1-5">
|
|
109
|
+
<span className="u-text-gray-400 u-me-3">Quality</span>
|
|
110
|
+
<span
|
|
111
|
+
className="u-font-bold u-text-uppercase"
|
|
112
|
+
style={{
|
|
113
|
+
fontSize: '0.6875rem', // 11px
|
|
114
|
+
color: getQualityColor(metrics.qualityLevel)
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{metrics.qualityLevel}
|
|
164
118
|
</span>
|
|
165
119
|
</div>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
120
|
+
|
|
121
|
+
{/* GPU Memory (if available) */}
|
|
122
|
+
{metrics.gpuMemory && (
|
|
123
|
+
<div className="u-flex u-items-center u-justify-between u-mb-1-5">
|
|
124
|
+
<span className="u-text-gray-400 u-me-3">GPU Memory</span>
|
|
125
|
+
<span className="u-font-bold">
|
|
126
|
+
~{Math.round(metrics.gpuMemory / 1024)}MB
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
{/* Auto-scaling notice */}
|
|
132
|
+
{metrics.isAutoScaling && (
|
|
133
|
+
<div
|
|
134
|
+
className="u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10 u-text-center"
|
|
135
|
+
style={{
|
|
136
|
+
fontSize: '0.625rem', // 10px
|
|
137
|
+
color: '#6b7280',
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
Auto-scaling active
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{/* Status indicator */}
|
|
145
|
+
<div className="u-flex u-items-center u-gap-2 u-mt-2 u-pt-2 u-border-t u-border-white-alpha-10">
|
|
146
|
+
<div
|
|
147
|
+
className="u-rounded-full"
|
|
148
|
+
style={{
|
|
149
|
+
width: '0.5rem',
|
|
150
|
+
height: '0.5rem',
|
|
151
|
+
flexShrink: 0,
|
|
152
|
+
backgroundColor: fpsColor,
|
|
153
|
+
...(isCritical && { animation: 'perf-dashboard-pulse 1s infinite' }),
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
<span
|
|
157
|
+
className="u-text-xs"
|
|
158
|
+
style={{
|
|
159
|
+
fontSize: '0.625rem', // 10px
|
|
160
|
+
color: fpsColor
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
{getFpsLabel(metrics.fps)}
|
|
164
|
+
</span>
|
|
179
165
|
</div>
|
|
180
|
-
)}
|
|
181
|
-
|
|
182
|
-
{/* Performance Status Indicator */}
|
|
183
|
-
<div style={{
|
|
184
|
-
marginTop: '8px',
|
|
185
|
-
paddingTop: '8px',
|
|
186
|
-
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
|
|
187
|
-
display: 'flex',
|
|
188
|
-
alignItems: 'center',
|
|
189
|
-
gap: '6px',
|
|
190
|
-
}}>
|
|
191
|
-
<div style={{
|
|
192
|
-
width: '8px',
|
|
193
|
-
height: '8px',
|
|
194
|
-
borderRadius: '50%',
|
|
195
|
-
backgroundColor: getFpsColor(metrics.fps),
|
|
196
|
-
animation: metrics.fps < 45 ? 'pulse 1s infinite' : 'none',
|
|
197
|
-
}} />
|
|
198
|
-
<span style={{
|
|
199
|
-
fontSize: '10px',
|
|
200
|
-
color: metrics.fps >= 58 ? '#4ade80' : metrics.fps >= 45 ? '#fbbf24' : '#ef4444',
|
|
201
|
-
}}>
|
|
202
|
-
{metrics.fps >= 58 ? 'Optimal' : metrics.fps >= 45 ? 'Warning' : 'Critical'}
|
|
203
|
-
</span>
|
|
204
166
|
</div>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
);
|
|
208
170
|
|
|
209
|
-
|
|
210
|
-
if (typeof document !== 'undefined') {
|
|
211
|
-
const styleSheet = document.createElement('style');
|
|
212
|
-
styleSheet.textContent = `
|
|
213
|
-
@keyframes pulse {
|
|
214
|
-
0%, 100% { opacity: 1; }
|
|
215
|
-
50% { opacity: 0.5; }
|
|
216
|
-
}
|
|
217
|
-
`;
|
|
218
|
-
document.head.appendChild(styleSheet);
|
|
219
|
-
}
|
|
171
|
+
PerformanceDashboard.displayName = 'PerformanceDashboard';
|