@shohojdhara/atomix 0.6.4 → 0.6.6
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/dist/atomix.css +117 -38
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/atomix.umd.js +1 -1
- package/dist/atomix.umd.js.map +1 -1
- package/dist/atomix.umd.min.js +1 -1
- package/dist/charts.d.ts +30 -1
- package/dist/charts.js +625 -846
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +30 -1
- package/dist/core.js +659 -873
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +30 -1
- package/dist/forms.js +1171 -1402
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +31 -89
- package/dist/heavy.js +975 -1195
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +383 -140
- package/dist/index.esm.js +1567 -1679
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1556 -1667
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +2 -5
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +14 -16
- package/src/components/AtomixGlass/AtomixGlass.tsx +137 -364
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +32 -251
- package/src/components/AtomixGlass/GlassFilter.tsx +62 -68
- package/src/components/AtomixGlass/README.md +2 -1
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +19 -18
- package/src/components/AtomixGlass/glass-border-styles.test.ts +58 -0
- package/src/components/AtomixGlass/glass-border-styles.ts +136 -0
- package/src/components/AtomixGlass/glass-utils.ts +456 -22
- package/src/components/AtomixGlass/shader-utils.ts +19 -77
- package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +158 -537
- package/src/components/AtomixGlass/stories/Border.stories.tsx +149 -0
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +229 -89
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +29 -340
- package/src/components/AtomixGlass/stories/argTypes.ts +30 -13
- package/src/components/AtomixGlass/stories/premium-presets.ts +206 -0
- package/src/components/AtomixGlass/stories/shared-components.tsx +52 -8
- package/src/components/Badge/Badge.tsx +4 -4
- package/src/components/Button/Button.tsx +2 -6
- package/src/components/Callout/Callout.test.tsx +4 -3
- package/src/components/Callout/Callout.tsx +2 -5
- package/src/components/Dropdown/Dropdown.tsx +3 -7
- package/src/components/Form/Checkbox.tsx +2 -8
- package/src/components/Form/Input.tsx +2 -9
- package/src/components/Form/Radio.tsx +2 -9
- package/src/components/Form/Select.test.tsx +6 -6
- package/src/components/Form/Select.tsx +2 -7
- package/src/components/Form/Textarea.stories.tsx +5 -5
- package/src/components/Form/Textarea.tsx +2 -9
- package/src/components/Messages/Messages.tsx +2 -8
- package/src/components/Modal/Modal.tsx +4 -5
- package/src/components/Navigation/Nav/Nav.tsx +2 -6
- package/src/components/Navigation/Navbar/Navbar.tsx +2 -9
- package/src/components/Navigation/SideMenu/SideMenu.tsx +2 -6
- package/src/components/Pagination/Pagination.tsx +2 -10
- package/src/components/Popover/Popover.tsx +2 -9
- package/src/components/Progress/Progress.tsx +2 -7
- package/src/components/Rating/Rating.tsx +2 -10
- package/src/components/Spinner/Spinner.tsx +2 -7
- package/src/components/Steps/Steps.tsx +2 -10
- package/src/components/Tabs/Tabs.tsx +2 -9
- package/src/components/Toggle/Toggle.tsx +2 -10
- package/src/components/Tooltip/Tooltip.tsx +2 -5
- package/src/lib/composables/useAtomixGlass.ts +42 -143
- package/src/lib/composables/useAtomixGlassStyles.ts +61 -77
- package/src/lib/composables/usePerformanceMonitor.ts +5 -66
- package/src/lib/constants/components.ts +363 -46
- package/src/lib/types/components.ts +33 -1
- package/src/styles/01-settings/_settings.atomix-glass.scss +66 -28
- package/src/styles/02-tools/_tools.button.scss +51 -42
- package/src/styles/02-tools/_tools.glass.scss +45 -3
- package/src/styles/06-components/_components.atomix-glass.scss +116 -79
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +0 -171
- package/src/components/AtomixGlass/animation-system.ts +0 -578
- package/src/components/AtomixGlass/deprecated/AtomixGlass.deprecated.tsx +0 -390
|
@@ -1,44 +1,32 @@
|
|
|
1
1
|
import React, { forwardRef, useId, useRef, useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import type { CSSProperties } from 'react';
|
|
3
2
|
import type {
|
|
4
3
|
DisplacementMode,
|
|
5
4
|
MousePosition,
|
|
6
5
|
GlassSize,
|
|
7
6
|
AtomixGlassProps,
|
|
8
7
|
} from '../../lib/types/components';
|
|
8
|
+
import useForkRef from '../../lib/utils/useForkRef';
|
|
9
|
+
import { mergeClassNames } from '../../lib/utils/componentUtils';
|
|
9
10
|
import type { FragmentShaderType, ShaderOptions, Vec2 } from './shader-utils';
|
|
10
11
|
import { GlassFilter } from './GlassFilter';
|
|
11
12
|
import {
|
|
12
|
-
calculateMouseInfluence,
|
|
13
|
-
clampBlur,
|
|
14
|
-
validateGlassSize,
|
|
15
13
|
getCachedShader,
|
|
14
|
+
getShaderAnimationTargetFps,
|
|
16
15
|
setCachedShader,
|
|
16
|
+
toSafeNumber,
|
|
17
|
+
validateGlassSize,
|
|
17
18
|
} from './glass-utils';
|
|
18
19
|
import { ATOMIX_GLASS } from '../../lib/constants/components';
|
|
19
20
|
|
|
20
|
-
const { CONSTANTS } = ATOMIX_GLASS;
|
|
21
|
-
|
|
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
21
|
|
|
34
|
-
// ─── Shader utility types ─────────────────────────────────────────────────────
|
|
35
22
|
|
|
23
|
+
/** Minimal interface for dynamically loaded shader displacement generators. */
|
|
36
24
|
interface ShaderGenerator {
|
|
37
25
|
updateShader(): string;
|
|
38
26
|
destroy(): void;
|
|
39
27
|
}
|
|
40
28
|
|
|
41
|
-
/** Fragment shader
|
|
29
|
+
/** Fragment shader signature; must match `shader-utils.ts`. */
|
|
42
30
|
type FragmentShaderFn = (uv: Vec2, mousePosition?: Vec2) => Vec2;
|
|
43
31
|
|
|
44
32
|
interface ShaderUtilsModule {
|
|
@@ -69,7 +57,6 @@ interface AtomixGlassContainerProps
|
|
|
69
57
|
onMouseEnter?: () => void;
|
|
70
58
|
onMouseDown?: () => void;
|
|
71
59
|
onMouseUp?: () => void;
|
|
72
|
-
isHovered?: boolean;
|
|
73
60
|
isActive?: boolean;
|
|
74
61
|
overLight?: boolean;
|
|
75
62
|
overLightConfig?: {
|
|
@@ -79,7 +66,7 @@ interface AtomixGlassContainerProps
|
|
|
79
66
|
borderOpacity?: number;
|
|
80
67
|
};
|
|
81
68
|
borderRadius?: number;
|
|
82
|
-
|
|
69
|
+
|
|
83
70
|
glassSize?: GlassSize;
|
|
84
71
|
onClick?: () => void;
|
|
85
72
|
mode?: DisplacementMode;
|
|
@@ -91,16 +78,15 @@ interface AtomixGlassContainerProps
|
|
|
91
78
|
isFixedOrSticky?: boolean;
|
|
92
79
|
elasticity?: number;
|
|
93
80
|
|
|
94
|
-
// Phase 1: Animation System props
|
|
95
|
-
shaderTime?: number;
|
|
96
|
-
|
|
97
81
|
contentRef?: React.RefObject<HTMLDivElement | null>;
|
|
98
82
|
children?: React.ReactNode;
|
|
99
83
|
}
|
|
100
84
|
|
|
101
85
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
86
|
+
* Internal glass surface that owns backdrop-filter, SVG distortion, and content.
|
|
87
|
+
*
|
|
88
|
+
* Layout and stacking styles are applied via the `style` prop from the parent.
|
|
89
|
+
* The root wrapper supplies CSS custom properties only.
|
|
104
90
|
*/
|
|
105
91
|
export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContainerProps>(
|
|
106
92
|
(
|
|
@@ -121,7 +107,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
121
107
|
overLight = false,
|
|
122
108
|
overLightConfig = {},
|
|
123
109
|
borderRadius = 0,
|
|
124
|
-
|
|
110
|
+
|
|
125
111
|
glassSize = { width: 0, height: 0 },
|
|
126
112
|
onClick,
|
|
127
113
|
mode = 'standard',
|
|
@@ -130,9 +116,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
130
116
|
shaderVariant = 'liquidGlass',
|
|
131
117
|
withLiquidBlur = false,
|
|
132
118
|
isFixedOrSticky = false,
|
|
133
|
-
|
|
134
|
-
// Phase 1: Animation System props
|
|
135
|
-
shaderTime,
|
|
136
119
|
withTimeAnimation = false,
|
|
137
120
|
animationSpeed = 1.0,
|
|
138
121
|
withMultiLayerDistortion = false,
|
|
@@ -148,6 +131,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
148
131
|
// React 18 useId — stable, unique, and SSR-safe (no module-level counter)
|
|
149
132
|
const rawId = useId();
|
|
150
133
|
const filterId = useMemo(() => `atomix-glass-filter-${rawId.replace(/:/g, '')}`, [rawId]);
|
|
134
|
+
const containerRef = useForkRef(ref, null);
|
|
151
135
|
|
|
152
136
|
const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
|
|
153
137
|
const shaderGeneratorRef = useRef<ShaderGenerator | null>(null);
|
|
@@ -156,7 +140,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
156
140
|
const shaderDebounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
157
141
|
const shaderUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
158
142
|
|
|
159
|
-
// Phase 1: Animation frame ref for continuous shader updates
|
|
160
143
|
const animationFrameRef = useRef<number | null>(null);
|
|
161
144
|
|
|
162
145
|
// Lazy load shader utilities only when shader mode is needed
|
|
@@ -260,7 +243,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
260
243
|
};
|
|
261
244
|
}, [mode, glassSize, shaderVariant]);
|
|
262
245
|
|
|
263
|
-
// Phase 1: Time-Based Animation Loop - Continuous shader regeneration
|
|
264
246
|
useEffect(() => {
|
|
265
247
|
// Only run animations in shader mode with time animation enabled
|
|
266
248
|
if (
|
|
@@ -277,27 +259,14 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
277
259
|
return;
|
|
278
260
|
}
|
|
279
261
|
|
|
280
|
-
const
|
|
281
|
-
distortionQuality
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const effectiveSpeed = Math.max(0.5, Math.min(2, animationSpeed || 1));
|
|
289
|
-
const complexity = withMultiLayerDistortion
|
|
290
|
-
? Math.max(
|
|
291
|
-
1,
|
|
292
|
-
(distortionOctaves || 3) / 3 +
|
|
293
|
-
Math.max(0, (distortionLacunarity || 2) - 2) * 0.25 +
|
|
294
|
-
Math.max(0, (distortionGain || 0.5) - 0.5)
|
|
295
|
-
)
|
|
296
|
-
: 1;
|
|
297
|
-
const targetFps = Math.max(
|
|
298
|
-
12,
|
|
299
|
-
Math.min(60, Math.round((baseFps * effectiveSpeed) / complexity))
|
|
300
|
-
);
|
|
262
|
+
const targetFps = getShaderAnimationTargetFps({
|
|
263
|
+
distortionQuality,
|
|
264
|
+
animationSpeed,
|
|
265
|
+
withMultiLayerDistortion,
|
|
266
|
+
distortionOctaves,
|
|
267
|
+
distortionLacunarity,
|
|
268
|
+
distortionGain,
|
|
269
|
+
});
|
|
301
270
|
const frameInterval = 1000 / targetFps;
|
|
302
271
|
let lastUpdate = 0;
|
|
303
272
|
let isCancelled = false;
|
|
@@ -350,212 +319,32 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
350
319
|
|
|
351
320
|
// Removed forced reflow to avoid layout thrash and potential feedback sizing loops
|
|
352
321
|
|
|
353
|
-
const [rectCache, setRectCache] = useState<DOMRect | null>(null);
|
|
354
|
-
|
|
355
|
-
useEffect(() => {
|
|
356
|
-
if (!ref || typeof ref === 'function') return undefined;
|
|
357
|
-
const element = (ref as React.RefObject<HTMLDivElement>).current;
|
|
358
|
-
if (!element) return undefined;
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
setRectCache(element.getBoundingClientRect());
|
|
362
|
-
} catch (error) {
|
|
363
|
-
console.warn('AtomixGlassContainer: Error getting element bounds', error);
|
|
364
|
-
setRectCache(null);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return undefined;
|
|
368
|
-
}, [ref, glassSize]);
|
|
369
|
-
|
|
370
|
-
const liquidBlur = useMemo(() => {
|
|
371
|
-
const defaultBlur = {
|
|
372
|
-
baseBlur: blurAmount,
|
|
373
|
-
edgeBlur: blurAmount * EDGE_BLUR_MULTIPLIER,
|
|
374
|
-
centerBlur: blurAmount * CENTER_BLUR_MULTIPLIER,
|
|
375
|
-
flowBlur: blurAmount * FLOW_BLUR_MULTIPLIER,
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
// Enhanced validation for liquid blur
|
|
379
|
-
if (
|
|
380
|
-
!withLiquidBlur ||
|
|
381
|
-
!rectCache ||
|
|
382
|
-
!mouseOffset ||
|
|
383
|
-
typeof mouseOffset.x !== 'number' ||
|
|
384
|
-
typeof mouseOffset.y !== 'number' ||
|
|
385
|
-
isNaN(mouseOffset.x) ||
|
|
386
|
-
isNaN(mouseOffset.y)
|
|
387
|
-
) {
|
|
388
|
-
return defaultBlur;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
try {
|
|
392
|
-
const mouseInfluence = calculateMouseInfluence(mouseOffset);
|
|
393
|
-
const maxBlur = blurAmount * MAX_BLUR_RELATIVE;
|
|
394
|
-
|
|
395
|
-
const baseBlur = Math.min(
|
|
396
|
-
maxBlur,
|
|
397
|
-
blurAmount + mouseInfluence * blurAmount * MOUSE_INFLUENCE_BLUR_FACTOR
|
|
398
|
-
);
|
|
399
|
-
const edgeIntensity = mouseInfluence * EDGE_INTENSITY_MOUSE_FACTOR;
|
|
400
|
-
const edgeBlur = Math.min(maxBlur, baseBlur * (0.8 + edgeIntensity * 0.4));
|
|
401
|
-
const centerIntensity = mouseInfluence * CENTER_INTENSITY_MOUSE_FACTOR;
|
|
402
|
-
const centerBlur = Math.min(maxBlur, baseBlur * (0.3 + centerIntensity * 0.3));
|
|
403
|
-
const flowBlur = Math.min(maxBlur, baseBlur * FLOW_BLUR_MULTIPLIER);
|
|
404
|
-
|
|
405
|
-
// NOTE: hover/active multipliers intentionally omitted here —
|
|
406
|
-
// they belong on opacity layers, not the blur filter itself.
|
|
407
|
-
return {
|
|
408
|
-
baseBlur: clampBlur(baseBlur),
|
|
409
|
-
edgeBlur: clampBlur(edgeBlur),
|
|
410
|
-
centerBlur: clampBlur(centerBlur),
|
|
411
|
-
flowBlur: clampBlur(flowBlur),
|
|
412
|
-
};
|
|
413
|
-
} catch (error) {
|
|
414
|
-
console.warn('AtomixGlassContainer: Error calculating liquid blur', error);
|
|
415
|
-
return defaultBlur;
|
|
416
|
-
}
|
|
417
|
-
}, [withLiquidBlur, blurAmount, mouseOffset, rectCache]);
|
|
418
322
|
|
|
419
|
-
const backdropStyle = useMemo(() => {
|
|
420
|
-
try {
|
|
421
|
-
const dynamicSaturation = saturation + (liquidBlur.baseBlur || 0) * 20;
|
|
422
|
-
|
|
423
|
-
// Validate blur values before using them
|
|
424
|
-
const validatedBaseBlur =
|
|
425
|
-
typeof liquidBlur.baseBlur === 'number' && !isNaN(liquidBlur.baseBlur)
|
|
426
|
-
? liquidBlur.baseBlur
|
|
427
|
-
: 0;
|
|
428
|
-
const validatedEdgeBlur =
|
|
429
|
-
typeof liquidBlur.edgeBlur === 'number' && !isNaN(liquidBlur.edgeBlur)
|
|
430
|
-
? liquidBlur.edgeBlur
|
|
431
|
-
: 0;
|
|
432
|
-
const validatedCenterBlur =
|
|
433
|
-
typeof liquidBlur.centerBlur === 'number' && !isNaN(liquidBlur.centerBlur)
|
|
434
|
-
? liquidBlur.centerBlur
|
|
435
|
-
: 0;
|
|
436
|
-
const validatedFlowBlur =
|
|
437
|
-
typeof liquidBlur.flowBlur === 'number' && !isNaN(liquidBlur.flowBlur)
|
|
438
|
-
? liquidBlur.flowBlur
|
|
439
|
-
: 0;
|
|
440
|
-
|
|
441
|
-
// Adaptive strategy: prefer single-pass blur for large areas or when effects are reduced
|
|
442
|
-
const area = rectCache ? rectCache.width * rectCache.height : 0;
|
|
443
|
-
const areaIsLarge = area > 180000; // ~600x300 threshold; tune as needed
|
|
444
|
-
const devicePrefersPerformance = effectiveReducedMotion || effectiveWithoutEffects;
|
|
445
|
-
const useMultiPass = withLiquidBlur && !devicePrefersPerformance && !areaIsLarge;
|
|
446
|
-
|
|
447
|
-
if (useMultiPass) {
|
|
448
|
-
// Use a single weighted-average blur instead of stacking multiple
|
|
449
|
-
// blur() calls. CSS blur() is additive — stacking 4 passes
|
|
450
|
-
// causes the perceived blur to compound far beyond any single value.
|
|
451
|
-
const weightedBlur = clampBlur(
|
|
452
|
-
validatedBaseBlur * 0.4 +
|
|
453
|
-
validatedEdgeBlur * 0.25 +
|
|
454
|
-
validatedCenterBlur * 0.15 +
|
|
455
|
-
validatedFlowBlur * 0.2
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
backdropFilter: `blur(${weightedBlur}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig?.contrast || 1}) brightness(${overLightConfig?.brightness || 1})`,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
323
|
|
|
463
|
-
// Single-pass fallback: stronger radius to match perceived blur of multi-pass
|
|
464
|
-
const effectiveBlur = clampBlur(
|
|
465
|
-
Math.max(
|
|
466
|
-
validatedBaseBlur,
|
|
467
|
-
validatedEdgeBlur * 0.8,
|
|
468
|
-
validatedCenterBlur * 1.1,
|
|
469
|
-
validatedFlowBlur * 0.9
|
|
470
|
-
)
|
|
471
|
-
);
|
|
472
324
|
|
|
473
|
-
return {
|
|
474
|
-
backdropFilter: `blur(${effectiveBlur}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig?.contrast || 1.05}) brightness(${overLightConfig?.brightness || 1.05})`,
|
|
475
|
-
};
|
|
476
|
-
} catch (error) {
|
|
477
|
-
console.warn('AtomixGlassContainer: Error calculating backdrop style', error);
|
|
478
|
-
return {
|
|
479
|
-
backdropFilter: `blur(${blurAmount}px) saturate(${saturation}%) contrast(1.05) brightness(1.05)`,
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
}, [
|
|
483
|
-
liquidBlur,
|
|
484
|
-
saturation,
|
|
485
|
-
blurAmount,
|
|
486
|
-
rectCache,
|
|
487
|
-
effectiveReducedMotion,
|
|
488
|
-
effectiveWithoutEffects,
|
|
489
|
-
withLiquidBlur,
|
|
490
|
-
overLightConfig,
|
|
491
|
-
]);
|
|
492
325
|
|
|
493
326
|
const containerVars = useMemo(() => {
|
|
494
327
|
try {
|
|
495
|
-
// Safe extraction of mouse offset values
|
|
496
|
-
const mx =
|
|
497
|
-
mouseOffset && typeof mouseOffset.x === 'number' && !isNaN(mouseOffset.x)
|
|
498
|
-
? mouseOffset.x
|
|
499
|
-
: 0;
|
|
500
|
-
const my =
|
|
501
|
-
mouseOffset && typeof mouseOffset.y === 'number' && !isNaN(mouseOffset.y)
|
|
502
|
-
? mouseOffset.y
|
|
503
|
-
: 0;
|
|
504
328
|
return {
|
|
505
329
|
'--atomix-glass-container-radius': `${typeof borderRadius === 'number' && !isNaN(borderRadius) ? borderRadius : 0}px`,
|
|
506
|
-
'--atomix-glass-container-backdrop': backdropStyle?.backdropFilter || 'none',
|
|
507
|
-
'--atomix-glass-container-shadow': overLight
|
|
508
|
-
? [
|
|
509
|
-
`inset 0 1px 0 rgba(255, 255, 255, ${(0.4 + mx * 0.002) * (overLightConfig?.shadowIntensity || 1)})`,
|
|
510
|
-
`inset 0 -1px 0 rgba(0, 0, 0, ${(0.2 + Math.abs(my) * 0.001) * (overLightConfig?.shadowIntensity || 1)})`,
|
|
511
|
-
`inset 0 0 20px rgba(0, 0, 0, ${(0.08 + Math.abs(mx + my) * 0.001) * (overLightConfig?.shadowIntensity || 1)})`,
|
|
512
|
-
`0 2px 12px rgba(0, 0, 0, ${(0.12 + Math.abs(my) * 0.002) * (overLightConfig?.shadowIntensity || 1)})`,
|
|
513
|
-
].join(', ')
|
|
514
|
-
: '0 0 20px rgba(0, 0, 0, 0.15) inset, 0 4px 8px rgba(0, 0, 0, 0.08) inset',
|
|
515
|
-
'--atomix-glass-container-shadow-opacity': effectiveWithoutEffects ? 0 : 1,
|
|
516
|
-
// Background and shadow values use design token-aligned RGB values
|
|
517
|
-
'--atomix-glass-container-bg': overLight
|
|
518
|
-
? `linear-gradient(${180 + mx * 0.5}deg, rgba(255, 255, 255, 0.1) 0%, transparent 20%, transparent 80%, rgba(0, 0, 0, 0.05) 100%)`
|
|
519
|
-
: 'none',
|
|
520
|
-
'--atomix-glass-container-text-shadow': overLight
|
|
521
|
-
? '0px 1px 2px rgba(255, 255, 255, 0.15)'
|
|
522
|
-
: '0px 2px 12px rgba(0, 0, 0, 0.4)',
|
|
523
|
-
'--atomix-glass-container-box-shadow': overLight
|
|
524
|
-
? '0px 16px 70px rgba(0, 0, 0, 0.75)'
|
|
525
|
-
: '0px 12px 40px rgba(0, 0, 0, 0.25)',
|
|
526
330
|
} as React.CSSProperties;
|
|
527
331
|
} catch (error) {
|
|
528
332
|
console.warn('AtomixGlassContainer: Error generating container variables', error);
|
|
529
333
|
return {
|
|
530
|
-
'--atomix-glass-container-padding': '0 0',
|
|
531
334
|
'--atomix-glass-container-radius': '0px',
|
|
532
|
-
'--atomix-glass-container-backdrop': 'none',
|
|
533
|
-
'--atomix-glass-container-shadow': 'none',
|
|
534
|
-
'--atomix-glass-container-shadow-opacity': 1,
|
|
535
|
-
'--atomix-glass-container-bg': 'none',
|
|
536
|
-
'--atomix-glass-container-text-shadow': 'none',
|
|
537
335
|
} as React.CSSProperties;
|
|
538
336
|
}
|
|
539
|
-
}, [
|
|
540
|
-
borderRadius,
|
|
541
|
-
backdropStyle,
|
|
542
|
-
mouseOffset,
|
|
543
|
-
overLight,
|
|
544
|
-
effectiveWithoutEffects,
|
|
545
|
-
overLightConfig,
|
|
546
|
-
]);
|
|
337
|
+
}, [borderRadius]);
|
|
547
338
|
|
|
548
339
|
return (
|
|
549
340
|
<div
|
|
550
|
-
ref={
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}}
|
|
558
|
-
className={`${ATOMIX_GLASS.CONTAINER_CLASS} ${className} ${isActive ? ATOMIX_GLASS.CLASSES.ACTIVE : ''} ${overLight ? ATOMIX_GLASS.CLASSES.OVER_LIGHT : ''}`}
|
|
341
|
+
ref={containerRef}
|
|
342
|
+
className={mergeClassNames(
|
|
343
|
+
ATOMIX_GLASS.CONTAINER_CLASS,
|
|
344
|
+
className,
|
|
345
|
+
isActive && ATOMIX_GLASS.CLASSES.ACTIVE,
|
|
346
|
+
overLight && ATOMIX_GLASS.CLASSES.OVER_LIGHT
|
|
347
|
+
)}
|
|
559
348
|
style={{ ...style, ...containerVars }}
|
|
560
349
|
onClick={onClick}
|
|
561
350
|
>
|
|
@@ -571,16 +360,8 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
571
360
|
blurAmount={blurAmount}
|
|
572
361
|
mode={mode}
|
|
573
362
|
id={filterId}
|
|
574
|
-
displacementScale={
|
|
575
|
-
|
|
576
|
-
? displacementScale
|
|
577
|
-
: 0
|
|
578
|
-
}
|
|
579
|
-
aberrationIntensity={
|
|
580
|
-
typeof aberrationIntensity === 'number' && !isNaN(aberrationIntensity)
|
|
581
|
-
? aberrationIntensity
|
|
582
|
-
: 0
|
|
583
|
-
}
|
|
363
|
+
displacementScale={toSafeNumber(displacementScale)}
|
|
364
|
+
aberrationIntensity={toSafeNumber(aberrationIntensity)}
|
|
584
365
|
shaderMapUrl={shaderMapUrl}
|
|
585
366
|
/>
|
|
586
367
|
{/* Enhanced Apple Liquid Glass Inner Shadow Layer */}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
2
|
import type { DisplacementMode } from '../../lib/types/components';
|
|
3
|
-
import
|
|
4
|
-
import { getDisplacementMap } from './glass-utils';
|
|
3
|
+
import { getChromaticDisplacementScale, getDisplacementMap } from './glass-utils';
|
|
5
4
|
import { displacementMap, polarDisplacementMap, prominentDisplacementMap } from './utils';
|
|
6
5
|
|
|
7
6
|
interface GlassFilterProps {
|
|
@@ -13,9 +12,43 @@ interface GlassFilterProps {
|
|
|
13
12
|
blurAmount: number;
|
|
14
13
|
}
|
|
15
14
|
|
|
15
|
+
/** Per-channel SVG filter configuration for chromatic aberration. */
|
|
16
|
+
const CHROMATIC_CHANNELS = [
|
|
17
|
+
{
|
|
18
|
+
result: 'RED_DISPLACED',
|
|
19
|
+
channelResult: 'RED_CHANNEL',
|
|
20
|
+
aberrationFactor: 0,
|
|
21
|
+
colorMatrix:
|
|
22
|
+
'1 0 0 0 0\n0 0 0 0 0\n0 0 0 0 0\n0 0 0 1 0',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
result: 'GREEN_DISPLACED',
|
|
26
|
+
channelResult: 'GREEN_CHANNEL',
|
|
27
|
+
aberrationFactor: 0.02,
|
|
28
|
+
colorMatrix:
|
|
29
|
+
'0 0 0 0 0\n0 1 0 0 0\n0 0 0 0 0\n0 0 0 1 0',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
result: 'BLUE_DISPLACED',
|
|
33
|
+
channelResult: 'BLUE_CHANNEL',
|
|
34
|
+
aberrationFactor: 0.03,
|
|
35
|
+
colorMatrix:
|
|
36
|
+
'0 0 0 0 0\n0 0 0 0 0\n0 0 1 0 0\n0 0 0 1 0',
|
|
37
|
+
},
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
const FILTER_SVG_STYLE: React.CSSProperties = {
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
width: '100%',
|
|
43
|
+
height: '100%',
|
|
44
|
+
inset: 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
16
47
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
48
|
+
* Renders an SVG filter definition for glass morphism distortion.
|
|
49
|
+
*
|
|
50
|
+
* Produces chromatic aberration at the edges via channel-separated displacement
|
|
51
|
+
* maps and recomposites the center region without distortion.
|
|
19
52
|
*/
|
|
20
53
|
const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
21
54
|
id,
|
|
@@ -25,15 +58,7 @@ const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
|
25
58
|
shaderMapUrl,
|
|
26
59
|
blurAmount,
|
|
27
60
|
}) => (
|
|
28
|
-
<svg
|
|
29
|
-
style={{
|
|
30
|
-
position: 'absolute',
|
|
31
|
-
width: '100%',
|
|
32
|
-
height: '100%',
|
|
33
|
-
inset: 0,
|
|
34
|
-
}}
|
|
35
|
-
aria-hidden="true"
|
|
36
|
-
>
|
|
61
|
+
<svg style={FILTER_SVG_STYLE} aria-hidden="true">
|
|
37
62
|
<defs>
|
|
38
63
|
<radialGradient id={`${id}-edge-mask`} cx="50%" cy="50%" r="50%">
|
|
39
64
|
<stop offset="0%" stopColor="black" stopOpacity="0" />
|
|
@@ -77,59 +102,29 @@ const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
|
77
102
|
|
|
78
103
|
<feOffset in="SourceGraphic" dx="0" dy="0" result="CENTER_ORIGINAL" />
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
yChannelSelector="B"
|
|
104
|
-
result="GREEN_DISPLACED"
|
|
105
|
-
/>
|
|
106
|
-
<feColorMatrix
|
|
107
|
-
in="GREEN_DISPLACED"
|
|
108
|
-
type="matrix"
|
|
109
|
-
values="0 0 0 0 0
|
|
110
|
-
0 1 0 0 0
|
|
111
|
-
0 0 0 0 0
|
|
112
|
-
0 0 0 1 0"
|
|
113
|
-
result="GREEN_CHANNEL"
|
|
114
|
-
/>
|
|
115
|
-
|
|
116
|
-
<feDisplacementMap
|
|
117
|
-
in="SourceGraphic"
|
|
118
|
-
in2="DISPLACEMENT_MAP"
|
|
119
|
-
scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.03)}
|
|
120
|
-
xChannelSelector="R"
|
|
121
|
-
yChannelSelector="B"
|
|
122
|
-
result="BLUE_DISPLACED"
|
|
123
|
-
/>
|
|
124
|
-
<feColorMatrix
|
|
125
|
-
in="BLUE_DISPLACED"
|
|
126
|
-
type="matrix"
|
|
127
|
-
values="0 0 0 0 0
|
|
128
|
-
0 0 0 0 0
|
|
129
|
-
0 0 1 0 0
|
|
130
|
-
0 0 0 1 0"
|
|
131
|
-
result="BLUE_CHANNEL"
|
|
132
|
-
/>
|
|
105
|
+
{CHROMATIC_CHANNELS.map(channel => (
|
|
106
|
+
<React.Fragment key={channel.channelResult}>
|
|
107
|
+
<feDisplacementMap
|
|
108
|
+
in="SourceGraphic"
|
|
109
|
+
in2="DISPLACEMENT_MAP"
|
|
110
|
+
scale={getChromaticDisplacementScale(
|
|
111
|
+
mode,
|
|
112
|
+
displacementScale,
|
|
113
|
+
aberrationIntensity,
|
|
114
|
+
channel.aberrationFactor
|
|
115
|
+
)}
|
|
116
|
+
xChannelSelector="R"
|
|
117
|
+
yChannelSelector="B"
|
|
118
|
+
result={channel.result}
|
|
119
|
+
/>
|
|
120
|
+
<feColorMatrix
|
|
121
|
+
in={channel.result}
|
|
122
|
+
type="matrix"
|
|
123
|
+
values={channel.colorMatrix}
|
|
124
|
+
result={channel.channelResult}
|
|
125
|
+
/>
|
|
126
|
+
</React.Fragment>
|
|
127
|
+
))}
|
|
133
128
|
|
|
134
129
|
<feBlend in="GREEN_CHANNEL" in2="BLUE_CHANNEL" mode="screen" result="GB_COMBINED" />
|
|
135
130
|
<feBlend in="RED_CHANNEL" in2="GB_COMBINED" mode="screen" result="RGB_COMBINED" />
|
|
@@ -160,9 +155,8 @@ const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
|
160
155
|
|
|
161
156
|
GlassFilterComponent.displayName = 'GlassFilter';
|
|
162
157
|
|
|
163
|
-
|
|
158
|
+
/** Shallow prop comparison to avoid redundant SVG filter regeneration. */
|
|
164
159
|
export const GlassFilter = memo(GlassFilterComponent, (prevProps, nextProps) => {
|
|
165
|
-
// Custom comparison: only re-render if props actually changed
|
|
166
160
|
return (
|
|
167
161
|
prevProps.id === nextProps.id &&
|
|
168
162
|
prevProps.displacementScale === nextProps.displacementScale &&
|
|
@@ -58,7 +58,8 @@ function MyComponent() {
|
|
|
58
58
|
| `debugOverLight` | boolean | false | Enable debug logging for overLight detection and configuration |
|
|
59
59
|
| `mode` | 'standard' \| 'polar' \| 'prominent' \| 'shader' | 'standard' | The glass effect mode |
|
|
60
60
|
| `onClick` | function | undefined | Click handler |
|
|
61
|
-
| `
|
|
61
|
+
| `border` | boolean \| GlassBorderConfig | true | Liquid glass rim (~0.5px). Structured: `{ enabled?, width?, opacity?, animated? }` |
|
|
62
|
+
| `withBorder` | boolean | true | **Deprecated** — use `border` (alias when `border` is omitted) |
|
|
62
63
|
| `withLiquidBlur` | boolean | false | Whether to enable liquid blur effects |
|
|
63
64
|
| `withoutEffects` | boolean | false | Whether to disable all visual effects |
|
|
64
65
|
| `reducedMotion` | boolean | false | Force reduced motion preference |
|