@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.
Files changed (83) hide show
  1. package/dist/atomix.css +117 -38
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +1 -1
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/atomix.umd.js +1 -1
  6. package/dist/atomix.umd.js.map +1 -1
  7. package/dist/atomix.umd.min.js +1 -1
  8. package/dist/charts.d.ts +30 -1
  9. package/dist/charts.js +625 -846
  10. package/dist/charts.js.map +1 -1
  11. package/dist/core.d.ts +30 -1
  12. package/dist/core.js +659 -873
  13. package/dist/core.js.map +1 -1
  14. package/dist/forms.d.ts +30 -1
  15. package/dist/forms.js +1171 -1402
  16. package/dist/forms.js.map +1 -1
  17. package/dist/heavy.d.ts +31 -89
  18. package/dist/heavy.js +975 -1195
  19. package/dist/heavy.js.map +1 -1
  20. package/dist/index.d.ts +383 -140
  21. package/dist/index.esm.js +1567 -1679
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.js +1556 -1667
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.min.js +1 -1
  26. package/dist/index.min.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/Accordion/Accordion.tsx +2 -5
  29. package/src/components/AtomixGlass/AtomixGlass.test.tsx +14 -16
  30. package/src/components/AtomixGlass/AtomixGlass.tsx +137 -364
  31. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +32 -251
  32. package/src/components/AtomixGlass/GlassFilter.tsx +62 -68
  33. package/src/components/AtomixGlass/README.md +2 -1
  34. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +19 -18
  35. package/src/components/AtomixGlass/glass-border-styles.test.ts +58 -0
  36. package/src/components/AtomixGlass/glass-border-styles.ts +136 -0
  37. package/src/components/AtomixGlass/glass-utils.ts +456 -22
  38. package/src/components/AtomixGlass/shader-utils.ts +19 -77
  39. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +158 -537
  40. package/src/components/AtomixGlass/stories/Border.stories.tsx +149 -0
  41. package/src/components/AtomixGlass/stories/Examples.stories.tsx +229 -89
  42. package/src/components/AtomixGlass/stories/Playground.stories.tsx +29 -340
  43. package/src/components/AtomixGlass/stories/argTypes.ts +30 -13
  44. package/src/components/AtomixGlass/stories/premium-presets.ts +206 -0
  45. package/src/components/AtomixGlass/stories/shared-components.tsx +52 -8
  46. package/src/components/Badge/Badge.tsx +4 -4
  47. package/src/components/Button/Button.tsx +2 -6
  48. package/src/components/Callout/Callout.test.tsx +4 -3
  49. package/src/components/Callout/Callout.tsx +2 -5
  50. package/src/components/Dropdown/Dropdown.tsx +3 -7
  51. package/src/components/Form/Checkbox.tsx +2 -8
  52. package/src/components/Form/Input.tsx +2 -9
  53. package/src/components/Form/Radio.tsx +2 -9
  54. package/src/components/Form/Select.test.tsx +6 -6
  55. package/src/components/Form/Select.tsx +2 -7
  56. package/src/components/Form/Textarea.stories.tsx +5 -5
  57. package/src/components/Form/Textarea.tsx +2 -9
  58. package/src/components/Messages/Messages.tsx +2 -8
  59. package/src/components/Modal/Modal.tsx +4 -5
  60. package/src/components/Navigation/Nav/Nav.tsx +2 -6
  61. package/src/components/Navigation/Navbar/Navbar.tsx +2 -9
  62. package/src/components/Navigation/SideMenu/SideMenu.tsx +2 -6
  63. package/src/components/Pagination/Pagination.tsx +2 -10
  64. package/src/components/Popover/Popover.tsx +2 -9
  65. package/src/components/Progress/Progress.tsx +2 -7
  66. package/src/components/Rating/Rating.tsx +2 -10
  67. package/src/components/Spinner/Spinner.tsx +2 -7
  68. package/src/components/Steps/Steps.tsx +2 -10
  69. package/src/components/Tabs/Tabs.tsx +2 -9
  70. package/src/components/Toggle/Toggle.tsx +2 -10
  71. package/src/components/Tooltip/Tooltip.tsx +2 -5
  72. package/src/lib/composables/useAtomixGlass.ts +42 -143
  73. package/src/lib/composables/useAtomixGlassStyles.ts +61 -77
  74. package/src/lib/composables/usePerformanceMonitor.ts +5 -66
  75. package/src/lib/constants/components.ts +363 -46
  76. package/src/lib/types/components.ts +33 -1
  77. package/src/styles/01-settings/_settings.atomix-glass.scss +66 -28
  78. package/src/styles/02-tools/_tools.button.scss +51 -42
  79. package/src/styles/02-tools/_tools.glass.scss +45 -3
  80. package/src/styles/06-components/_components.atomix-glass.scss +116 -79
  81. package/src/components/AtomixGlass/PerformanceDashboard.tsx +0 -171
  82. package/src/components/AtomixGlass/animation-system.ts +0 -578
  83. 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 function signature matches shader-utils.ts */
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
- padding?: string;
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
- * AtomixGlassContainer - Internal container component for glass effects
103
- * Handles the visual glass morphism layer with filters and backdrop effects
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
- padding = '0 0',
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 baseFps =
281
- distortionQuality === 'ultra'
282
- ? 60
283
- : distortionQuality === 'high'
284
- ? 30
285
- : distortionQuality === 'medium'
286
- ? 24
287
- : 20;
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={el => {
551
- // Handle forwarded ref
552
- if (typeof ref === 'function') {
553
- ref(el);
554
- } else if (ref) {
555
- (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;
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
- typeof displacementScale === 'number' && !isNaN(displacementScale)
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 type { FragmentShaderType } from './shader-utils';
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
- * GlassFilter - SVG filter component for glass morphism effects
18
- * Creates chromatic aberration and edge distortion effects using SVG filters
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
- <feDisplacementMap
81
- in="SourceGraphic"
82
- in2="DISPLACEMENT_MAP"
83
- scale={displacementScale * (mode === 'shader' ? 1 : -1)}
84
- xChannelSelector="R"
85
- yChannelSelector="B"
86
- result="RED_DISPLACED"
87
- />
88
- <feColorMatrix
89
- in="RED_DISPLACED"
90
- type="matrix"
91
- values="1 0 0 0 0
92
- 0 0 0 0 0
93
- 0 0 0 0 0
94
- 0 0 0 1 0"
95
- result="RED_CHANNEL"
96
- />
97
-
98
- <feDisplacementMap
99
- in="SourceGraphic"
100
- in2="DISPLACEMENT_MAP"
101
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.02)}
102
- xChannelSelector="R"
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
- // Memoize component to prevent unnecessary re-renders
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
- | `withBorder` | boolean | true | Whether to show border effects |
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 |