@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.
Files changed (77) hide show
  1. package/dist/atomix.css +119 -40
  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 +566 -597
  10. package/dist/charts.js.map +1 -1
  11. package/dist/core.d.ts +30 -1
  12. package/dist/core.js +600 -624
  13. package/dist/core.js.map +1 -1
  14. package/dist/forms.d.ts +30 -1
  15. package/dist/forms.js +1122 -1163
  16. package/dist/forms.js.map +1 -1
  17. package/dist/heavy.d.ts +31 -89
  18. package/dist/heavy.js +1015 -1045
  19. package/dist/heavy.js.map +1 -1
  20. package/dist/index.d.ts +378 -104
  21. package/dist/index.esm.js +10959 -10837
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.js +10935 -10812
  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 -355
  31. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +32 -249
  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 +411 -6
  38. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +158 -537
  39. package/src/components/AtomixGlass/stories/Border.stories.tsx +149 -0
  40. package/src/components/AtomixGlass/stories/Examples.stories.tsx +229 -89
  41. package/src/components/AtomixGlass/stories/Playground.stories.tsx +29 -340
  42. package/src/components/AtomixGlass/stories/argTypes.ts +30 -13
  43. package/src/components/AtomixGlass/stories/premium-presets.ts +206 -0
  44. package/src/components/AtomixGlass/stories/shared-components.tsx +52 -8
  45. package/src/components/Badge/Badge.tsx +4 -4
  46. package/src/components/Button/Button.tsx +2 -6
  47. package/src/components/Callout/Callout.test.tsx +4 -3
  48. package/src/components/Callout/Callout.tsx +2 -5
  49. package/src/components/Dropdown/Dropdown.tsx +3 -7
  50. package/src/components/Form/Checkbox.tsx +2 -8
  51. package/src/components/Form/Input.tsx +2 -9
  52. package/src/components/Form/Radio.tsx +2 -9
  53. package/src/components/Form/Select.tsx +2 -7
  54. package/src/components/Form/Textarea.tsx +2 -9
  55. package/src/components/Messages/Messages.tsx +2 -8
  56. package/src/components/Modal/Modal.tsx +4 -5
  57. package/src/components/Navigation/Nav/Nav.tsx +2 -6
  58. package/src/components/Navigation/Navbar/Navbar.tsx +2 -9
  59. package/src/components/Navigation/SideMenu/SideMenu.tsx +2 -6
  60. package/src/components/Pagination/Pagination.tsx +2 -10
  61. package/src/components/Popover/Popover.tsx +2 -9
  62. package/src/components/Progress/Progress.tsx +2 -7
  63. package/src/components/Rating/Rating.tsx +2 -10
  64. package/src/components/Spinner/Spinner.tsx +2 -7
  65. package/src/components/Steps/Steps.tsx +2 -10
  66. package/src/components/Tabs/Tabs.tsx +2 -9
  67. package/src/components/Toggle/Toggle.tsx +2 -10
  68. package/src/components/Tooltip/Tooltip.tsx +2 -5
  69. package/src/lib/composables/useAtomixGlass.ts +41 -10
  70. package/src/lib/composables/useAtomixGlassStyles.ts +59 -75
  71. package/src/lib/composables/usePerformanceMonitor.ts +5 -0
  72. package/src/lib/constants/components.ts +358 -46
  73. package/src/lib/types/components.ts +33 -1
  74. package/src/styles/01-settings/_settings.atomix-glass.scss +69 -31
  75. package/src/styles/02-tools/_tools.glass.scss +45 -3
  76. package/src/styles/06-components/_components.atomix-glass.scss +114 -77
  77. package/src/components/AtomixGlass/deprecated/AtomixGlass.deprecated.tsx +0 -390
@@ -1,12 +1,19 @@
1
1
  import React, { forwardRef, memo, useMemo, useRef } from 'react';
2
2
  import type { AtomixGlassProps } from '../../lib/types/components';
3
3
  import { ATOMIX_GLASS } from '../../lib/constants/components';
4
+ import { mergeClassNames } from '../../lib/utils/componentUtils';
5
+ import useForkRef from '../../lib/utils/useForkRef';
4
6
  import { AtomixGlassContainer } from './AtomixGlassContainer';
7
+ import {
8
+ buildGlassRootCssVariables,
9
+ getGlassInternalPositionStyles,
10
+ isGlassFixedOrSticky,
11
+ resolveGlassAdjustedSize,
12
+ resolveGlassContainerEffects,
13
+ } from './glass-utils';
5
14
  import { useAtomixGlass } from '../../lib/composables/useAtomixGlass';
6
- // Phase 3: Optimization & Adaptation
7
15
  import { useResponsiveGlass } from '../../lib/composables/useResponsiveGlass';
8
16
  import { usePerformanceMonitor } from '../../lib/composables/usePerformanceMonitor';
9
- import { PerformanceDashboard } from './PerformanceDashboard';
10
17
  import {
11
18
  getDevicePreset,
12
19
  MOBILE_OPTIMIZED_BREAKPOINTS,
@@ -35,6 +42,10 @@ import {
35
42
  * - Implements focus-ring mixin for accessibility
36
43
  * - Supports reduced motion and high contrast preferences
37
44
  *
45
+ * Style architecture:
46
+ * - Root (`.c-atomix-glass`): CSS custom properties for layer geometry and motion.
47
+ * - Container (`.c-atomix-glass__container`): layout, z-index, and backdrop-filter.
48
+ *
38
49
  * @example
39
50
  * // Basic usage with dynamic border-radius extraction
40
51
  * <AtomixGlass>
@@ -92,29 +103,7 @@ import {
92
103
  * </AtomixGlass>
93
104
  */
94
105
 
95
- // ─── Type guard for the dev-only performance flag ────────────────────────────
96
- declare global {
97
- interface Window {
98
- enablePerformanceMonitoring?: boolean;
99
- }
100
- }
101
-
102
- // Helper to merge refs
103
- function mergeRefs<T = any>(
104
- ...refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined | null)[]
105
- ) {
106
- return (node: T) => {
107
- refs.forEach(ref => {
108
- if (typeof ref === 'function') {
109
- ref(node);
110
- } else if (ref != null) {
111
- (ref as React.MutableRefObject<T | null>).current = node;
112
- }
113
- });
114
- };
115
- }
116
-
117
- // Internal implementation with forwardRef
106
+ /** Internal implementation; ref is forwarded to the root wrapper element. */
118
107
  const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function AtomixGlass(
119
108
  {
120
109
  children,
@@ -128,7 +117,7 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
128
117
  mouseOffset: externalMouseOffset,
129
118
  mouseContainer = null,
130
119
  className = '',
131
- padding = ATOMIX_GLASS.DEFAULTS.PADDING,
120
+
132
121
  overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
133
122
  style = {},
134
123
  mode = ATOMIX_GLASS.DEFAULTS.MODE,
@@ -142,7 +131,9 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
142
131
  highContrast = false,
143
132
  withoutEffects = false,
144
133
  withLiquidBlur = false,
134
+ border,
145
135
  withBorder = true,
136
+ debugBorderRadius = false,
146
137
  withOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS,
147
138
  debugPerformance = false,
148
139
  debugOverLight = false,
@@ -165,21 +156,18 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
165
156
  const glassRef = useRef<HTMLDivElement>(null);
166
157
  const contentRef = useRef<HTMLDivElement>(null);
167
158
  const internalWrapperRef = useRef<HTMLDivElement>(null);
168
- const mergedRef = useMemo(() => mergeRefs(ref, internalWrapperRef), [ref]);
169
-
170
- // ── Layout hoisting ──────────────────────────────────────────────────────
171
- // When position is fixed/sticky the layout props must live on the ROOT
172
- // `.c-atomix-glass` element so that every decorative layer (borders,
173
- // backgrounds, hover effects) stays in the same stacking context.
174
-
175
- // Extract zIndex from style so it becomes the base for ALL internal
176
- // layers via --atomix-glass-base-z-index. It must NOT be applied as a
177
- // real z-index on the root element — that would break the glass effect.
159
+ const mergedRef = useForkRef(ref, internalWrapperRef);
160
+
161
+ /**
162
+ * Style partitioning for backdrop-filter compatibility.
163
+ * - Root (`.c-atomix-glass`): CSS custom properties only (`glassVars`).
164
+ * - Container (`.c-atomix-glass__container`): layout, stacking, and visual styles.
165
+ * Backdrop sampling occurs on `__filter-overlay` inside the container; layout
166
+ * properties must not be applied to the root or the effect will not render correctly.
167
+ */
178
168
  const { zIndex: customZIndex, ...restStyle } = style;
179
- const isFixedOrSticky =
180
- propsIsFixedOrSticky || restStyle.position === 'fixed' || restStyle.position === 'sticky';
169
+ const isFixedOrSticky = isGlassFixedOrSticky(propsIsFixedOrSticky, restStyle.position);
181
170
 
182
- // Use composable hook for all state and logic
183
171
  const {
184
172
  isHovered,
185
173
  isActive,
@@ -198,6 +186,7 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
198
186
  handleMouseDown,
199
187
  handleMouseUp,
200
188
  handleKeyDown,
189
+ resolvedBorder,
201
190
  } = useAtomixGlass({
202
191
  glassRef,
203
192
  contentRef,
@@ -218,10 +207,12 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
218
207
  blurAmount,
219
208
  saturation,
220
209
  withLiquidBlur,
221
- padding,
210
+ border,
211
+ withBorder,
212
+ debugBorderRadius,
213
+
222
214
  style,
223
215
  isFixedOrSticky,
224
- // Phase 1: Animation System props
225
216
  withTimeAnimation,
226
217
  animationSpeed,
227
218
  withMultiLayerDistortion,
@@ -231,16 +222,11 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
231
222
  distortionQuality,
232
223
  });
233
224
 
234
- // ============================================================================
235
- // Phase 3: Optimization & Adaptation Systems
236
- // ============================================================================
237
225
 
238
- // Get device preset parameters - memoized to prevent recalculation
239
226
  const devicePresetParams = useMemo(() => {
240
227
  return getDevicePreset(devicePreset);
241
228
  }, [devicePreset]);
242
229
 
243
- // Responsive breakpoint system - automatically adjusts parameters based on viewport
244
230
  useResponsiveGlass({
245
231
  baseParams: {
246
232
  ...devicePresetParams,
@@ -259,309 +245,142 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
259
245
  debug: false,
260
246
  });
261
247
 
262
- // Performance monitoring - tracks FPS, frame time, memory usage
263
- const { metrics: performanceMetrics, toggleMonitoring } = usePerformanceMonitor({
264
- enabled: debugPerformance, // Enable when debugPerformance is true
248
+ usePerformanceMonitor({
249
+ enabled: debugPerformance,
265
250
  debug: false,
266
251
  showOverlay: false,
267
252
  });
268
253
 
269
- // Auto-start performance monitoring when debugPerformance is enabled
270
- React.useEffect(() => {
271
- if (debugPerformance) {
272
- toggleMonitoring();
273
- }
274
- // eslint-disable-next-line react-hooks/exhaustive-deps
275
- }, [debugPerformance]); // Re-run when debugPerformance changes
276
-
277
254
  const isOverLight = useMemo(() => overLightConfig.isOverLight, [overLightConfig.isOverLight]);
278
255
 
279
256
  const shouldRenderOverLightLayers = withOverLightLayers && isOverLight;
280
257
 
281
- const rootLayoutStyle = useMemo<React.CSSProperties>(() => {
282
- if (!isFixedOrSticky) return {};
283
- const { position: p, top: t, left: l, right: r, bottom: b } = restStyle;
284
- return {
285
- ...(p && { position: p }),
286
- ...(t !== undefined && { top: t }),
287
- ...(l !== undefined && { left: l }),
288
- ...(r !== undefined && { right: r }),
289
- ...(b !== undefined && { bottom: b }),
290
- };
291
- }, [isFixedOrSticky, restStyle]);
292
-
293
- // Calculate base style with transforms
294
- // When layout is hoisted to the root, strip those props from the container
295
- const baseStyle = useMemo(() => {
296
- if (isFixedOrSticky) {
297
- const { position: _p, top: _t, left: _l, right: _r, bottom: _b, ...visualStyle } = restStyle;
298
- return {
299
- ...visualStyle,
300
- };
301
- }
302
- return {
258
+ const containerStyle = useMemo(
259
+ () => ({
303
260
  ...restStyle,
304
- };
305
- }, [isFixedOrSticky, restStyle]);
261
+ ...(customZIndex !== undefined && { zIndex: customZIndex }),
262
+ }),
263
+ [restStyle, customZIndex]
264
+ );
306
265
 
307
- // Build className with state modifiers
308
- const componentClassName = [
266
+ const componentClassName = mergeClassNames(
309
267
  ATOMIX_GLASS.BASE_CLASS,
310
268
  effectiveReducedMotion && `${ATOMIX_GLASS.BASE_CLASS}--reduced-motion`,
311
269
  effectiveHighContrast && `${ATOMIX_GLASS.BASE_CLASS}--high-contrast`,
312
270
  effectiveWithoutEffects && `${ATOMIX_GLASS.BASE_CLASS}--disabled-effects`,
313
- className,
314
- ]
315
- .filter(Boolean)
316
- .join(' ');
271
+ className
272
+ );
317
273
 
318
- // Calculate position and size styles for internal layers
319
- // When root is fixed/sticky, internal layers use absolute (relative to root)
320
274
  const positionStyles = useMemo(
321
- () => ({
322
- position: (isFixedOrSticky
323
- ? 'absolute'
324
- : restStyle.position || 'absolute') as React.CSSProperties['position'],
325
- top: !isFixedOrSticky ? 0 : (restStyle.top ?? 0),
326
- left: !isFixedOrSticky ? 0 : (restStyle.left ?? 0),
327
- right: !isFixedOrSticky ? 'auto' : (restStyle.right ?? 'auto'),
328
- bottom: !isFixedOrSticky ? 'auto' : (restStyle.bottom ?? 'auto'),
329
- }),
275
+ () => getGlassInternalPositionStyles(isFixedOrSticky, restStyle),
276
+ [isFixedOrSticky, restStyle]
277
+ );
278
+
279
+ const adjustedSize = useMemo(
280
+ () =>
281
+ resolveGlassAdjustedSize({
282
+ width,
283
+ height,
284
+ restStyle,
285
+ glassSize,
286
+ isFixedOrSticky,
287
+ }),
288
+ [width, height, restStyle, glassSize, isFixedOrSticky]
289
+ );
290
+
291
+ const glassVars = useMemo(
292
+ () =>
293
+ buildGlassRootCssVariables({
294
+ effectiveBorderRadius,
295
+ transformStyle,
296
+ adjustedSize,
297
+ isOverLight,
298
+ customZIndex,
299
+ isFixedOrSticky,
300
+ positionStyles,
301
+ restStyle,
302
+ borderWidth: resolvedBorder.width,
303
+ }),
330
304
  [
305
+ effectiveBorderRadius,
306
+ transformStyle,
307
+ adjustedSize,
308
+ isOverLight,
309
+ customZIndex,
331
310
  isFixedOrSticky,
332
- restStyle.position,
333
- restStyle.top,
334
- restStyle.left,
335
- restStyle.right,
336
- restStyle.bottom,
311
+ positionStyles,
312
+ restStyle,
313
+ resolvedBorder.width,
337
314
  ]
338
315
  );
339
316
 
340
- const adjustedSize = useMemo(() => {
341
- // Keep a reference to positionStyles to avoid unused-variable lint,
342
- // but sizing is driven by explicit width/height or measured size.
343
- const _position = positionStyles.position;
344
-
345
- const resolveLength = (value: string | number | undefined, measured: number): string => {
346
- if (value !== undefined && isFixedOrSticky) {
347
- return typeof value === 'number' ? `${value}px` : value;
348
- }
349
-
350
- if (measured > 0 && isFixedOrSticky) {
351
- return `${measured}px`;
352
- }
353
-
354
- return '100%';
355
- };
356
-
357
- const effectiveWidth = width ?? restStyle.width;
358
- const effectiveHeight = height ?? restStyle.height;
359
-
360
- return {
361
- width: resolveLength(effectiveWidth, glassSize.width),
362
- height: resolveLength(effectiveHeight, glassSize.height),
363
- };
364
- }, [
365
- width,
366
- height,
367
- restStyle.width,
368
- restStyle.height,
369
- positionStyles.position,
370
- glassSize.width,
371
- glassSize.height,
372
- isFixedOrSticky,
373
- ]);
374
-
375
- // Memoize expensive gradient calculations
376
- const gradientValues = useMemo(() => {
377
- const mx = mouseOffset.x;
378
- const my = mouseOffset.y;
379
- const absMx = Math.abs(mx);
380
- const absMy = Math.abs(my);
381
- const GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
382
-
383
- return {
384
- borderGradientAngle: GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER,
385
- borderStop1: Math.max(
386
- GRADIENT.BORDER_STOP_1.MIN,
387
- GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER
388
- ),
389
- borderStop2: Math.min(
390
- GRADIENT.BORDER_STOP_2.MAX,
391
- GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER
392
- ),
393
- borderOpacities: [
394
- GRADIENT.BORDER_OPACITY.BASE_1 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW,
395
- GRADIENT.BORDER_OPACITY.BASE_2 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH,
396
- GRADIENT.BORDER_OPACITY.BASE_3 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW,
397
- GRADIENT.BORDER_OPACITY.BASE_4 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH,
398
- ],
399
- hoverPositions: {
400
- hover1: {
401
- x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_1,
402
- y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_1,
403
- },
404
- hover2: {
405
- x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_2,
406
- y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_2,
407
- },
408
- hover3: {
409
- x: GRADIENT.CENTER_POSITION + mx * GRADIENT.HOVER_POSITION.MULTIPLIER_3,
410
- y: GRADIENT.CENTER_POSITION + my * GRADIENT.HOVER_POSITION.MULTIPLIER_3,
411
- },
412
- },
413
- basePosition: {
414
- x: GRADIENT.CENTER_POSITION + mx * GRADIENT.BASE_LAYER_MULTIPLIER,
415
- y: GRADIENT.CENTER_POSITION + my * GRADIENT.BASE_LAYER_MULTIPLIER,
416
- },
417
- mx,
418
- my,
419
- absMx,
420
- absMy,
421
- };
422
- }, [mouseOffset.x, mouseOffset.y]);
423
-
424
- // Clamp overLight opacities to [0,1]
425
- const clampedOverLightOpacity = Math.max(0, Math.min(1, overLightConfig?.opacity ?? 0.4));
426
- const clampedBorderOpacity = Math.max(0, Math.min(1, overLightConfig?.borderOpacity ?? 1));
427
-
428
- // Memoize opacity calculations
429
- const opacityValues = useMemo(() => {
430
- // Use clamped opacity value here
431
- const overLightOpacity = clampedOverLightOpacity;
432
- const BASE_OVER_LIGHT_OPACITY = 0.4;
433
- const OVER_OPACITY_MULTIPLIER = 1.1;
434
-
435
- return {
436
- hover1: isHovered || isActive ? 0.5 : 0,
437
- hover2: isActive ? 0.5 : 0,
438
- hover3: isHovered ? 0.4 : isActive ? 0.8 : 0,
439
- base: isOverLight ? overLightOpacity || BASE_OVER_LIGHT_OPACITY : 0,
440
- over: isOverLight
441
- ? (overLightOpacity || BASE_OVER_LIGHT_OPACITY) * OVER_OPACITY_MULTIPLIER
442
- : 0,
443
- };
444
- }, [isHovered, isActive, isOverLight, clampedOverLightOpacity]);
445
-
446
- // Memoize CSS variables object
447
- const glassVars = useMemo(() => {
448
- const whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE;
449
- const blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK;
450
- const {
451
- borderGradientAngle,
452
- borderStop1,
453
- borderStop2,
454
- borderOpacities,
455
- hoverPositions,
456
- basePosition,
457
- mx,
458
- my,
459
- absMx,
460
- absMy,
461
- } = gradientValues;
462
-
463
- return {
464
- ...(customZIndex !== undefined && { '--atomix-glass-base-z-index': customZIndex }),
465
- '--atomix-glass-radius': `${effectiveBorderRadius}px`,
466
- '--atomix-glass-transform': transformStyle || 'none',
467
- '--atomix-glass-container-position': `${!isFixedOrSticky ? positionStyles.position : rootLayoutStyle.position}`,
468
- '--atomix-glass-position': `${!isFixedOrSticky ? positionStyles.position : rootLayoutStyle.position}`,
469
- '--atomix-glass-top': `${!isFixedOrSticky ? 0 : (restStyle.top ?? 0)}px`,
470
- '--atomix-glass-left': `${!isFixedOrSticky ? 0 : (restStyle.left ?? 0)}px`,
471
- '--atomix-glass-right': !isFixedOrSticky ? 'auto' : (restStyle.right ?? 'auto'),
472
- '--atomix-glass-bottom': !isFixedOrSticky ? 'auto' : (restStyle.bottom ?? 'auto'),
473
- '--atomix-glass-width': adjustedSize.width,
474
- '--atomix-glass-height': adjustedSize.height,
475
- '--atomix-glass-border-width': 'var(--atomix-spacing-0-5, 0.125rem)',
476
- '--atomix-glass-blend-mode': isOverLight ? 'multiply' : 'overlay',
477
- '--atomix-glass-border-gradient-1': `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[0] ?? 1) * clampedBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[1] ?? 1) * clampedBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`,
478
- '--atomix-glass-border-gradient-2': `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[2] ?? 1) * clampedBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[3] ?? 1) * clampedBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`,
479
- '--atomix-glass-hover-1-opacity': opacityValues.hover1,
480
- '--atomix-glass-hover-1-gradient': isOverLight
481
- ? `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_END}%)`
482
- : `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_STOP}%)`,
483
- '--atomix-glass-hover-2-opacity': opacityValues.hover2,
484
- '--atomix-glass-hover-2-gradient': isOverLight
485
- ? `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_END}%)`
486
- : `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_STOP}%)`,
487
- '--atomix-glass-hover-3-opacity': opacityValues.hover3,
488
- '--atomix-glass-hover-3-gradient': isOverLight
489
- ? `radial-gradient(circle at ${hoverPositions.hover3.x}% ${hoverPositions.hover3.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_END}%)`
490
- : `radial-gradient(circle at ${hoverPositions.hover3.x}% ${hoverPositions.hover3.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_STOP}%)`,
491
- '--atomix-glass-base-opacity': opacityValues.base,
492
- '--atomix-glass-base-gradient': isOverLight
493
- ? `linear-gradient(${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.ANGLE}deg, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_BASE + mx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_BASE + my * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_MULTIPLIER}) ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_STOP}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_BASE + absMx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
494
- : `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.WHITE_OPACITY})`,
495
- '--atomix-glass-overlay-opacity': opacityValues.over,
496
- '--atomix-glass-overlay-gradient': isOverLight
497
- ? `radial-gradient(circle at ${basePosition.x}% ${basePosition.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_BASE + absMx * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_MID_STOP}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_BASE + absMy * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
498
- : `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.WHITE_OPACITY})`,
499
- '--atomix-glass-overlay-highlight-opacity':
500
- opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
501
- '--atomix-glass-overlay-highlight-bg': `radial-gradient(circle at ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.POSITION_X}% ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.POSITION_Y}%, rgba(255, 255, 255, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.WHITE_OPACITY}) 0%, transparent ${ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.STOP}%)`,
502
- } as React.CSSProperties;
503
- }, [
504
- gradientValues,
505
- opacityValues,
506
- effectiveBorderRadius,
507
- transformStyle,
508
- adjustedSize,
509
- isOverLight,
510
- clampedBorderOpacity,
511
- customZIndex,
512
- isFixedOrSticky,
513
- positionStyles.position,
514
- rootLayoutStyle.position,
515
- restStyle.top,
516
- restStyle.left,
517
- restStyle.right,
518
- restStyle.bottom,
519
- ]);
520
-
521
- // ─── Render helpers ──────────────────────────────────────────────────────
317
+ const containerEffects = useMemo(
318
+ () =>
319
+ resolveGlassContainerEffects({
320
+ displacementScale,
321
+ blurAmount,
322
+ saturation,
323
+ aberrationIntensity,
324
+ mode,
325
+ effectiveWithoutEffects,
326
+ effectiveHighContrast,
327
+ isOverLight,
328
+ saturationBoost: overLightConfig.saturationBoost,
329
+ mouseOffset,
330
+ globalMousePosition,
331
+ }),
332
+ [
333
+ displacementScale,
334
+ blurAmount,
335
+ saturation,
336
+ aberrationIntensity,
337
+ mode,
338
+ effectiveWithoutEffects,
339
+ effectiveHighContrast,
340
+ isOverLight,
341
+ overLightConfig.saturationBoost,
342
+ mouseOffset,
343
+ globalMousePosition,
344
+ ]
345
+ );
522
346
 
523
347
  const renderHoverLayers = () => (
524
348
  <>
525
- {/* Hover layers - opacity and background set via CSS variables in SCSS */}
526
- <div className={ATOMIX_GLASS.HOVER_1_CLASS} />
527
- <div className={ATOMIX_GLASS.HOVER_2_CLASS} />
528
- <div className={ATOMIX_GLASS.HOVER_3_CLASS} />
349
+ <div aria-hidden="true" className={ATOMIX_GLASS.HOVER_1_CLASS} />
350
+ <div aria-hidden="true" className={ATOMIX_GLASS.HOVER_2_CLASS} />
351
+ <div aria-hidden="true" className={ATOMIX_GLASS.HOVER_3_CLASS} />
529
352
  </>
530
353
  );
531
354
 
532
- const renderBackgroundLayer = (layerType: 'dark' | 'black') => (
355
+ const backgroundLayerTypes = ['dark', 'black'] as const;
356
+ const renderBackgroundLayer = (layerType: (typeof backgroundLayerTypes)[number]) => (
533
357
  <div
534
- className={[
358
+ aria-hidden="true"
359
+ className={mergeClassNames(
535
360
  ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
536
361
  layerType === 'dark'
537
362
  ? ATOMIX_GLASS.BACKGROUND_LAYER_DARK_CLASS
538
363
  : ATOMIX_GLASS.BACKGROUND_LAYER_BLACK_CLASS,
539
364
  isOverLight
540
365
  ? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
541
- : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
542
- ]
543
- .filter(Boolean)
544
- .join(' ')}
366
+ : ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS
367
+ )}
545
368
  />
546
369
  );
547
370
 
548
371
  const renderOverLightLayers = () => (
549
372
  <>
550
- {/* Base and overlay layers - opacity and background set via CSS variables in SCSS */}
551
- <div className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
552
- <div className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
553
- {/* Overlay highlight - opacity and background are dynamic, calculated inline */}
554
- <div className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS} />
373
+ <div aria-hidden="true" className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
374
+ <div aria-hidden="true" className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
375
+ <div aria-hidden="true" className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS} />
555
376
  </>
556
377
  );
557
378
 
558
379
  const renderBorderElements = () => (
559
380
  <>
560
- {/* Border elements - all styles (static and dynamic via CSS variables) are in SCSS */}
561
- {/* Position, size, transform, transition, border-radius all use CSS variables set in glassVars */}
562
- <span className={ATOMIX_GLASS.BORDER_BACKDROP_CLASS} />
563
- <span className={ATOMIX_GLASS.BORDER_1_CLASS} />
564
- <span className={ATOMIX_GLASS.BORDER_2_CLASS} />
381
+ <span aria-hidden="true" className={ATOMIX_GLASS.BORDER_BACKDROP_CLASS} />
382
+ <span aria-hidden="true" className={ATOMIX_GLASS.BORDER_1_CLASS} />
383
+ <span aria-hidden="true" className={ATOMIX_GLASS.BORDER_2_CLASS} />
565
384
  </>
566
385
  );
567
386
 
@@ -570,54 +389,31 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
570
389
  {...rest}
571
390
  ref={mergedRef}
572
391
  className={componentClassName}
573
- style={{ ...glassVars }}
392
+ style={glassVars}
574
393
  role={role || (onClick ? 'button' : undefined)}
575
394
  tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
576
395
  aria-label={ariaLabel}
577
396
  aria-describedby={ariaDescribedBy}
578
397
  aria-disabled={onClick && effectiveWithoutEffects ? true : onClick ? false : undefined}
579
- aria-pressed={onClick ? isActive : undefined}
580
398
  onKeyDown={onClick ? handleKeyDown : undefined}
581
399
  >
582
400
  <AtomixGlassContainer
583
401
  ref={glassRef}
584
402
  contentRef={contentRef}
585
403
  className={className}
586
- style={{ ...restStyle } as React.CSSProperties}
404
+ style={containerStyle as React.CSSProperties}
587
405
  borderRadius={effectiveBorderRadius}
588
- displacementScale={
589
- effectiveWithoutEffects
590
- ? 0
591
- : mode === 'shader'
592
- ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT
593
- : isOverLight
594
- ? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT
595
- : displacementScale
596
- }
597
- blurAmount={effectiveWithoutEffects ? 0 : blurAmount}
598
- saturation={
599
- effectiveHighContrast
600
- ? ATOMIX_GLASS.CONSTANTS.SATURATION.HIGH_CONTRAST
601
- : isOverLight
602
- ? saturation * overLightConfig.saturationBoost
603
- : saturation
604
- }
605
- aberrationIntensity={
606
- effectiveWithoutEffects
607
- ? 0
608
- : mode === 'shader'
609
- ? aberrationIntensity * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_ABERRATION
610
- : aberrationIntensity
611
- }
406
+ displacementScale={containerEffects.displacementScale}
407
+ blurAmount={containerEffects.blurAmount}
408
+ saturation={containerEffects.saturation}
409
+ aberrationIntensity={containerEffects.aberrationIntensity}
612
410
  glassSize={glassSize}
613
- padding={padding}
614
- mouseOffset={effectiveWithoutEffects ? { x: 0, y: 0 } : mouseOffset}
615
- globalMousePosition={effectiveWithoutEffects ? { x: 0, y: 0 } : globalMousePosition}
411
+ mouseOffset={containerEffects.mouseOffset}
412
+ globalMousePosition={containerEffects.globalMousePosition}
616
413
  onMouseEnter={handleMouseEnter}
617
414
  onMouseLeave={handleMouseLeave}
618
415
  onMouseDown={handleMouseDown}
619
416
  onMouseUp={handleMouseUp}
620
- isHovered={isHovered}
621
417
  isActive={isActive}
622
418
  overLight={isOverLight}
623
419
  overLightConfig={{
@@ -633,7 +429,6 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
633
429
  shaderVariant={shaderVariant}
634
430
  withLiquidBlur={withLiquidBlur}
635
431
  isFixedOrSticky={isFixedOrSticky}
636
- // Phase 1: Animation System props
637
432
  shaderTime={getShaderTime()}
638
433
  withTimeAnimation={withTimeAnimation}
639
434
  animationSpeed={animationSpeed}
@@ -648,32 +443,19 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
648
443
 
649
444
  {Boolean(onClick) && renderHoverLayers()}
650
445
 
651
- {/* Background layers for over-light mode */}
652
- {/* Static styles (pointer-events) are in SCSS; will-change is managed via .u-glass-clean-root utility for backdrop-filter stability */}
653
- {renderBackgroundLayer('dark')}
654
- {renderBackgroundLayer('black')}
446
+ {backgroundLayerTypes.map(layerType => (
447
+ <React.Fragment key={layerType}>{renderBackgroundLayer(layerType)}</React.Fragment>
448
+ ))}
655
449
  {shouldRenderOverLightLayers && renderOverLightLayers()}
656
450
 
657
- {withBorder && renderBorderElements()}
658
-
659
- {/* Phase 3: Performance Monitoring Dashboard - Only in development with debugPerformance enabled */}
660
- {debugPerformance && performanceMetrics && (
661
- <PerformanceDashboard
662
- metrics={performanceMetrics}
663
- isVisible={true}
664
- onClose={() => {}} // No-op, dashboard always visible when debugPerformance is true
665
- />
666
- )}
451
+ {resolvedBorder.enabled && renderBorderElements()}
667
452
  </div>
668
453
  );
669
454
  });
670
455
 
671
456
  AtomixGlassInner.displayName = 'AtomixGlass';
672
457
 
673
- /**
674
- * AtomixGlass - wrapped with React.memo to prevent unnecessary re-renders.
675
- * Ref is forwarded to the root `<div>` element.
676
- */
458
+ /** Memoized public export. Ref targets the root `.c-atomix-glass` wrapper. */
677
459
  export const AtomixGlass = memo(AtomixGlassInner);
678
460
 
679
461
  export default AtomixGlass;