@shohojdhara/atomix 0.6.3 → 0.6.5
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 +119 -40
- 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 +566 -597
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +30 -1
- package/dist/core.js +600 -624
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +30 -1
- package/dist/forms.js +1122 -1163
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +31 -89
- package/dist/heavy.js +1015 -1045
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +378 -104
- package/dist/index.esm.js +10959 -10837
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +10935 -10812
- 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 -355
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +32 -249
- 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 +411 -6
- 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.tsx +2 -7
- 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 +41 -10
- package/src/lib/composables/useAtomixGlassStyles.ts +59 -75
- package/src/lib/composables/usePerformanceMonitor.ts +5 -0
- package/src/lib/constants/components.ts +358 -46
- package/src/lib/types/components.ts +33 -1
- package/src/styles/01-settings/_settings.atomix-glass.scss +69 -31
- package/src/styles/02-tools/_tools.glass.scss +45 -3
- package/src/styles/06-components/_components.atomix-glass.scss +114 -77
- 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;
|
|
@@ -90,8 +77,6 @@ interface AtomixGlassContainerProps
|
|
|
90
77
|
withLiquidBlur?: boolean;
|
|
91
78
|
isFixedOrSticky?: boolean;
|
|
92
79
|
elasticity?: number;
|
|
93
|
-
|
|
94
|
-
// Phase 1: Animation System props
|
|
95
80
|
shaderTime?: number;
|
|
96
81
|
|
|
97
82
|
contentRef?: React.RefObject<HTMLDivElement | null>;
|
|
@@ -99,8 +84,10 @@ interface AtomixGlassContainerProps
|
|
|
99
84
|
}
|
|
100
85
|
|
|
101
86
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
87
|
+
* Internal glass surface that owns backdrop-filter, SVG distortion, and content.
|
|
88
|
+
*
|
|
89
|
+
* Layout and stacking styles are applied via the `style` prop from the parent.
|
|
90
|
+
* The root wrapper supplies CSS custom properties only.
|
|
104
91
|
*/
|
|
105
92
|
export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContainerProps>(
|
|
106
93
|
(
|
|
@@ -121,7 +108,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
121
108
|
overLight = false,
|
|
122
109
|
overLightConfig = {},
|
|
123
110
|
borderRadius = 0,
|
|
124
|
-
|
|
111
|
+
|
|
125
112
|
glassSize = { width: 0, height: 0 },
|
|
126
113
|
onClick,
|
|
127
114
|
mode = 'standard',
|
|
@@ -130,8 +117,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
130
117
|
shaderVariant = 'liquidGlass',
|
|
131
118
|
withLiquidBlur = false,
|
|
132
119
|
isFixedOrSticky = false,
|
|
133
|
-
|
|
134
|
-
// Phase 1: Animation System props
|
|
135
120
|
shaderTime,
|
|
136
121
|
withTimeAnimation = false,
|
|
137
122
|
animationSpeed = 1.0,
|
|
@@ -148,6 +133,7 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
148
133
|
// React 18 useId — stable, unique, and SSR-safe (no module-level counter)
|
|
149
134
|
const rawId = useId();
|
|
150
135
|
const filterId = useMemo(() => `atomix-glass-filter-${rawId.replace(/:/g, '')}`, [rawId]);
|
|
136
|
+
const containerRef = useForkRef(ref, null);
|
|
151
137
|
|
|
152
138
|
const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
|
|
153
139
|
const shaderGeneratorRef = useRef<ShaderGenerator | null>(null);
|
|
@@ -156,7 +142,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
156
142
|
const shaderDebounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
157
143
|
const shaderUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
158
144
|
|
|
159
|
-
// Phase 1: Animation frame ref for continuous shader updates
|
|
160
145
|
const animationFrameRef = useRef<number | null>(null);
|
|
161
146
|
|
|
162
147
|
// Lazy load shader utilities only when shader mode is needed
|
|
@@ -260,7 +245,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
260
245
|
};
|
|
261
246
|
}, [mode, glassSize, shaderVariant]);
|
|
262
247
|
|
|
263
|
-
// Phase 1: Time-Based Animation Loop - Continuous shader regeneration
|
|
264
248
|
useEffect(() => {
|
|
265
249
|
// Only run animations in shader mode with time animation enabled
|
|
266
250
|
if (
|
|
@@ -277,27 +261,14 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
277
261
|
return;
|
|
278
262
|
}
|
|
279
263
|
|
|
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
|
-
);
|
|
264
|
+
const targetFps = getShaderAnimationTargetFps({
|
|
265
|
+
distortionQuality,
|
|
266
|
+
animationSpeed,
|
|
267
|
+
withMultiLayerDistortion,
|
|
268
|
+
distortionOctaves,
|
|
269
|
+
distortionLacunarity,
|
|
270
|
+
distortionGain,
|
|
271
|
+
});
|
|
301
272
|
const frameInterval = 1000 / targetFps;
|
|
302
273
|
let lastUpdate = 0;
|
|
303
274
|
let isCancelled = false;
|
|
@@ -350,212 +321,32 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
350
321
|
|
|
351
322
|
// Removed forced reflow to avoid layout thrash and potential feedback sizing loops
|
|
352
323
|
|
|
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
324
|
|
|
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
325
|
|
|
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
326
|
|
|
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
327
|
|
|
493
328
|
const containerVars = useMemo(() => {
|
|
494
329
|
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
330
|
return {
|
|
505
331
|
'--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
332
|
} as React.CSSProperties;
|
|
527
333
|
} catch (error) {
|
|
528
334
|
console.warn('AtomixGlassContainer: Error generating container variables', error);
|
|
529
335
|
return {
|
|
530
|
-
'--atomix-glass-container-padding': '0 0',
|
|
531
336
|
'--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
337
|
} as React.CSSProperties;
|
|
538
338
|
}
|
|
539
|
-
}, [
|
|
540
|
-
borderRadius,
|
|
541
|
-
backdropStyle,
|
|
542
|
-
mouseOffset,
|
|
543
|
-
overLight,
|
|
544
|
-
effectiveWithoutEffects,
|
|
545
|
-
overLightConfig,
|
|
546
|
-
]);
|
|
339
|
+
}, [borderRadius]);
|
|
547
340
|
|
|
548
341
|
return (
|
|
549
342
|
<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 : ''}`}
|
|
343
|
+
ref={containerRef}
|
|
344
|
+
className={mergeClassNames(
|
|
345
|
+
ATOMIX_GLASS.CONTAINER_CLASS,
|
|
346
|
+
className,
|
|
347
|
+
isActive && ATOMIX_GLASS.CLASSES.ACTIVE,
|
|
348
|
+
overLight && ATOMIX_GLASS.CLASSES.OVER_LIGHT
|
|
349
|
+
)}
|
|
559
350
|
style={{ ...style, ...containerVars }}
|
|
560
351
|
onClick={onClick}
|
|
561
352
|
>
|
|
@@ -571,16 +362,8 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
|
|
|
571
362
|
blurAmount={blurAmount}
|
|
572
363
|
mode={mode}
|
|
573
364
|
id={filterId}
|
|
574
|
-
displacementScale={
|
|
575
|
-
|
|
576
|
-
? displacementScale
|
|
577
|
-
: 0
|
|
578
|
-
}
|
|
579
|
-
aberrationIntensity={
|
|
580
|
-
typeof aberrationIntensity === 'number' && !isNaN(aberrationIntensity)
|
|
581
|
-
? aberrationIntensity
|
|
582
|
-
: 0
|
|
583
|
-
}
|
|
365
|
+
displacementScale={toSafeNumber(displacementScale)}
|
|
366
|
+
aberrationIntensity={toSafeNumber(aberrationIntensity)}
|
|
584
367
|
shaderMapUrl={shaderMapUrl}
|
|
585
368
|
/>
|
|
586
369
|
{/* 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 |
|