@shohojdhara/atomix 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atomix.css +2 -0
- 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 +9 -0
- package/dist/charts.js +43 -8
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +9 -0
- package/dist/core.js +43 -8
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +9 -0
- package/dist/forms.js +43 -8
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +9 -0
- package/dist/heavy.js +43 -8
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.esm.js +43 -8
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +43 -8
- 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/AtomixGlass/deprecated/AtomixGlass.deprecated.tsx +390 -0
- package/src/lib/composables/shared-mouse-tracker.ts +62 -6
- package/src/lib/composables/useAtomixGlass.ts +4 -2
- package/src/lib/constants/components.ts +4 -0
- package/src/lib/types/components.ts +10 -4
- package/src/styles/06-components/_components.navbar.scss +2 -0
- package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +0 -215
package/package.json
CHANGED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import React, { useId, useMemo, useRef } from 'react';
|
|
2
|
+
import type { AtomixGlassProps } from '../../../lib/types/components';
|
|
3
|
+
import { ATOMIX_GLASS } from '../../../lib/constants/components';
|
|
4
|
+
import { AtomixGlassContainer } from '../AtomixGlassContainer';
|
|
5
|
+
import { useAtomixGlass } from '../../../lib/composables/useAtomixGlass';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AtomixGlass - A high-performance glass morphism component with liquid distortion effects
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Hardware-accelerated glass effects with SVG filters
|
|
12
|
+
* - Mouse-responsive liquid distortion
|
|
13
|
+
* - Dynamic border-radius extraction from children CSS properties
|
|
14
|
+
* - Automatic light/dark theme detection
|
|
15
|
+
* - Accessibility and performance optimizations
|
|
16
|
+
* - Multiple displacement modes (standard, polar, prominent, shader)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Dynamic border-radius extraction
|
|
20
|
+
* <AtomixGlass>
|
|
21
|
+
* <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
|
|
22
|
+
* </AtomixGlass>
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Manual border-radius override
|
|
26
|
+
* <AtomixGlass cornerRadius={20}>
|
|
27
|
+
* <div>Content with 20px glass radius</div>
|
|
28
|
+
* </AtomixGlass>
|
|
29
|
+
*/
|
|
30
|
+
export function AtomixGlass({
|
|
31
|
+
children,
|
|
32
|
+
displacementScale = ATOMIX_GLASS.DEFAULTS.DISPLACEMENT_SCALE,
|
|
33
|
+
blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT,
|
|
34
|
+
saturation = ATOMIX_GLASS.DEFAULTS.SATURATION,
|
|
35
|
+
aberrationIntensity = ATOMIX_GLASS.DEFAULTS.ABERRATION_INTENSITY,
|
|
36
|
+
elasticity = ATOMIX_GLASS.DEFAULTS.ELASTICITY,
|
|
37
|
+
borderRadius,
|
|
38
|
+
globalMousePosition: externalGlobalMousePosition,
|
|
39
|
+
mouseOffset: externalMouseOffset,
|
|
40
|
+
mouseContainer = null,
|
|
41
|
+
className = '',
|
|
42
|
+
padding = ATOMIX_GLASS.DEFAULTS.PADDING,
|
|
43
|
+
overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
|
|
44
|
+
style = {},
|
|
45
|
+
mode = ATOMIX_GLASS.DEFAULTS.MODE,
|
|
46
|
+
onClick,
|
|
47
|
+
shaderVariant = 'liquidGlass',
|
|
48
|
+
'aria-label': ariaLabel,
|
|
49
|
+
'aria-describedby': ariaDescribedBy,
|
|
50
|
+
role,
|
|
51
|
+
tabIndex,
|
|
52
|
+
reducedMotion = false,
|
|
53
|
+
highContrast = false,
|
|
54
|
+
withoutEffects = false,
|
|
55
|
+
withLiquidBlur = false,
|
|
56
|
+
withBorder = true,
|
|
57
|
+
withOverLightLayers = false,
|
|
58
|
+
debugBorderRadius = false,
|
|
59
|
+
}: AtomixGlassProps) {
|
|
60
|
+
const glassRef = useRef<HTMLDivElement>(null);
|
|
61
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
62
|
+
|
|
63
|
+
// Use composable hook for all state and logic
|
|
64
|
+
const {
|
|
65
|
+
isHovered,
|
|
66
|
+
isActive,
|
|
67
|
+
glassSize,
|
|
68
|
+
effectiveBorderRadius,
|
|
69
|
+
effectiveReducedMotion,
|
|
70
|
+
effectiveHighContrast,
|
|
71
|
+
effectiveWithoutEffects,
|
|
72
|
+
overLightConfig,
|
|
73
|
+
globalMousePosition,
|
|
74
|
+
mouseOffset,
|
|
75
|
+
transformStyle,
|
|
76
|
+
handleMouseEnter,
|
|
77
|
+
handleMouseLeave,
|
|
78
|
+
handleMouseDown,
|
|
79
|
+
handleMouseUp,
|
|
80
|
+
handleKeyDown,
|
|
81
|
+
} = useAtomixGlass({
|
|
82
|
+
glassRef,
|
|
83
|
+
contentRef,
|
|
84
|
+
borderRadius,
|
|
85
|
+
globalMousePosition: externalGlobalMousePosition,
|
|
86
|
+
mouseOffset: externalMouseOffset,
|
|
87
|
+
mouseContainer,
|
|
88
|
+
overLight,
|
|
89
|
+
reducedMotion,
|
|
90
|
+
highContrast,
|
|
91
|
+
withoutEffects,
|
|
92
|
+
elasticity,
|
|
93
|
+
onClick,
|
|
94
|
+
debugBorderRadius,
|
|
95
|
+
children,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Calculate base style with transforms
|
|
99
|
+
const baseStyle = useMemo(
|
|
100
|
+
() => ({
|
|
101
|
+
...style,
|
|
102
|
+
...(elasticity !== 0 && {
|
|
103
|
+
transform: transformStyle,
|
|
104
|
+
transition: effectiveReducedMotion ? 'none' : 'all ease-out 0.2s',
|
|
105
|
+
willChange: effectiveWithoutEffects ? 'auto' : 'transform',
|
|
106
|
+
}),
|
|
107
|
+
...(effectiveHighContrast && {
|
|
108
|
+
border: '2px solid currentColor',
|
|
109
|
+
outline: '2px solid transparent',
|
|
110
|
+
outlineOffset: '2px',
|
|
111
|
+
}),
|
|
112
|
+
}),
|
|
113
|
+
[
|
|
114
|
+
style,
|
|
115
|
+
transformStyle,
|
|
116
|
+
effectiveReducedMotion,
|
|
117
|
+
effectiveWithoutEffects,
|
|
118
|
+
effectiveHighContrast,
|
|
119
|
+
elasticity,
|
|
120
|
+
]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Calculate position and size styles
|
|
124
|
+
const positionStyles = useMemo(
|
|
125
|
+
() => ({
|
|
126
|
+
position: (baseStyle.position || 'absolute') as React.CSSProperties['position'],
|
|
127
|
+
top: baseStyle.top || 0,
|
|
128
|
+
left: baseStyle.left || 0,
|
|
129
|
+
}),
|
|
130
|
+
[baseStyle]
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const adjustedSize = useMemo(
|
|
134
|
+
() => ({
|
|
135
|
+
width:
|
|
136
|
+
baseStyle.position !== 'fixed'
|
|
137
|
+
? '100%'
|
|
138
|
+
: baseStyle.width
|
|
139
|
+
? baseStyle.width
|
|
140
|
+
: Math.max(glassSize.width, 0),
|
|
141
|
+
height:
|
|
142
|
+
baseStyle.position !== 'fixed'
|
|
143
|
+
? '100%'
|
|
144
|
+
: baseStyle.height
|
|
145
|
+
? baseStyle.height
|
|
146
|
+
: Math.max(glassSize.height, 0),
|
|
147
|
+
}),
|
|
148
|
+
[baseStyle, glassSize]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Generate CSS variables for layers
|
|
152
|
+
const glassId = useId();
|
|
153
|
+
const glassVars = useMemo(() => {
|
|
154
|
+
const isOverLight = overLightConfig?.isOverLight ?? false;
|
|
155
|
+
const mx = mouseOffset.x;
|
|
156
|
+
const my = mouseOffset.y;
|
|
157
|
+
const scopedId = `ag-${glassId.replace(/:/g, '')}`;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
[`--${scopedId}-pos`]: positionStyles.position,
|
|
161
|
+
[`--${scopedId}-top`]: positionStyles.top !== 'fixed' ? `${positionStyles.top}px` : '0',
|
|
162
|
+
[`--${scopedId}-left`]: positionStyles.left !== 'fixed' ? `${positionStyles.left}px` : '0',
|
|
163
|
+
[`--${scopedId}-w`]:
|
|
164
|
+
baseStyle.position !== 'fixed' ? adjustedSize.width : `${adjustedSize.width}px`,
|
|
165
|
+
[`--${scopedId}-h`]:
|
|
166
|
+
baseStyle.position !== 'fixed' ? adjustedSize.height : `${adjustedSize.height}px`,
|
|
167
|
+
[`--${scopedId}-r`]: `${effectiveBorderRadius}px`,
|
|
168
|
+
[`--${scopedId}-t`]: baseStyle.transform,
|
|
169
|
+
[`--${scopedId}-tr`]: effectiveReducedMotion ? 'none' : baseStyle.transition,
|
|
170
|
+
[`--${scopedId}-blend`]: isOverLight ? 'multiply' : 'overlay',
|
|
171
|
+
[`--${scopedId}-b1`]: `linear-gradient(${135 + mx * 1.2}deg, rgba(255,255,255,0) 0%, rgba(255,255,255,${0.12 + Math.abs(mx) * 0.008}) ${Math.max(10, 33 + my * 0.3)}%, rgba(255,255,255,${0.4 + Math.abs(mx) * 0.012}) ${Math.min(90, 66 + my * 0.4)}%, rgba(255,255,255,0) 100%)`,
|
|
172
|
+
[`--${scopedId}-b2`]: `linear-gradient(${135 + mx * 1.2}deg, rgba(255,255,255,0) 0%, rgba(255,255,255,${0.32 + Math.abs(mx) * 0.008}) ${Math.max(10, 33 + my * 0.3)}%, rgba(255,255,255,${0.6 + Math.abs(mx) * 0.012}) ${Math.min(90, 66 + my * 0.4)}%, rgba(255,255,255,0) 100%)`,
|
|
173
|
+
[`--${scopedId}-h1-o`]: isHovered || isActive ? (isOverLight ? 0.3 : 0.5) : 0,
|
|
174
|
+
[`--${scopedId}-h1`]: isOverLight
|
|
175
|
+
? `radial-gradient(circle at ${50 + mx / 2}% ${50 + my / 2}%, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.05) 30%, rgba(0,0,0,0) 60%)`
|
|
176
|
+
: `radial-gradient(circle at ${50 + mx / 2}% ${50 + my / 2}%, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%)`,
|
|
177
|
+
[`--${scopedId}-h2-o`]: isActive ? (isOverLight ? 0.4 : 0.5) : 0,
|
|
178
|
+
[`--${scopedId}-h2`]: isOverLight
|
|
179
|
+
? `radial-gradient(circle at ${50 + mx / 1.5}% ${50 + my / 1.5}%, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.1) 40%, rgba(0,0,0,0) 80%)`
|
|
180
|
+
: `radial-gradient(circle at ${50 + mx / 1.5}% ${50 + my / 1.5}%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 80%)`,
|
|
181
|
+
[`--${scopedId}-h3-o`]: isHovered
|
|
182
|
+
? isOverLight
|
|
183
|
+
? 0.25
|
|
184
|
+
: 0.4
|
|
185
|
+
: isActive
|
|
186
|
+
? isOverLight
|
|
187
|
+
? 0.5
|
|
188
|
+
: 0.8
|
|
189
|
+
: 0,
|
|
190
|
+
[`--${scopedId}-h3`]: isOverLight
|
|
191
|
+
? `radial-gradient(circle at ${50 + mx}% ${50 + my}%, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0) 100%)`
|
|
192
|
+
: `radial-gradient(circle at ${50 + mx}% ${50 + my}%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%)`,
|
|
193
|
+
[`--${scopedId}-base-o`]: isOverLight ? overLightConfig.opacity : 0,
|
|
194
|
+
[`--${scopedId}-base`]: isOverLight
|
|
195
|
+
? `linear-gradient(135deg, rgba(0,0,0,${0.12 + mx * 0.002}) 0%, rgba(0,0,0,${0.08 + my * 0.001}) 50%, rgba(0,0,0,${0.15 + Math.abs(mx) * 0.003}) 100%)`
|
|
196
|
+
: 'rgba(255,255,255,0.1)',
|
|
197
|
+
[`--${scopedId}-over-o`]: isOverLight ? overLightConfig.opacity * 0.9 : 0,
|
|
198
|
+
[`--${scopedId}-over`]: isOverLight
|
|
199
|
+
? `radial-gradient(circle at ${50 + mx * 0.5}% ${50 + my * 0.5}%, rgba(0,0,0,${0.08 + Math.abs(mx) * 0.002}) 0%, rgba(0,0,0,0.04) 40%, rgba(0,0,0,${0.12 + Math.abs(my) * 0.002}) 100%)`
|
|
200
|
+
: 'rgba(255,255,255,0.05)',
|
|
201
|
+
'--ag-scoped-id': scopedId,
|
|
202
|
+
} as React.CSSProperties;
|
|
203
|
+
}, [
|
|
204
|
+
glassId,
|
|
205
|
+
positionStyles,
|
|
206
|
+
adjustedSize,
|
|
207
|
+
effectiveBorderRadius,
|
|
208
|
+
baseStyle,
|
|
209
|
+
effectiveReducedMotion,
|
|
210
|
+
mouseOffset,
|
|
211
|
+
isHovered,
|
|
212
|
+
isActive,
|
|
213
|
+
overLightConfig,
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
const scopedId = `ag-${glassId.replace(/:/g, '')}`;
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<div
|
|
220
|
+
className={`${ATOMIX_GLASS.BASE_CLASS} ${className}`}
|
|
221
|
+
style={{ ...positionStyles, position: 'relative', ...glassVars }}
|
|
222
|
+
role={role || (onClick ? 'button' : undefined)}
|
|
223
|
+
tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
|
|
224
|
+
aria-label={ariaLabel}
|
|
225
|
+
aria-describedby={ariaDescribedBy}
|
|
226
|
+
aria-disabled={onClick ? false : undefined}
|
|
227
|
+
onKeyDown={onClick ? handleKeyDown : undefined}
|
|
228
|
+
>
|
|
229
|
+
<AtomixGlassContainer
|
|
230
|
+
ref={glassRef}
|
|
231
|
+
contentRef={contentRef}
|
|
232
|
+
className={className}
|
|
233
|
+
style={baseStyle}
|
|
234
|
+
borderRadius={effectiveBorderRadius}
|
|
235
|
+
displacementScale={
|
|
236
|
+
effectiveWithoutEffects
|
|
237
|
+
? 0
|
|
238
|
+
: mode === 'shader'
|
|
239
|
+
? displacementScale * 0.8
|
|
240
|
+
: overLightConfig.isOverLight
|
|
241
|
+
? displacementScale * 0.6
|
|
242
|
+
: displacementScale
|
|
243
|
+
}
|
|
244
|
+
blurAmount={effectiveWithoutEffects ? 0 : blurAmount}
|
|
245
|
+
saturation={
|
|
246
|
+
effectiveHighContrast
|
|
247
|
+
? 200
|
|
248
|
+
: overLightConfig.isOverLight
|
|
249
|
+
? saturation * overLightConfig.saturationBoost
|
|
250
|
+
: saturation
|
|
251
|
+
}
|
|
252
|
+
aberrationIntensity={
|
|
253
|
+
effectiveWithoutEffects
|
|
254
|
+
? 0
|
|
255
|
+
: mode === 'shader'
|
|
256
|
+
? aberrationIntensity * 0.7
|
|
257
|
+
: aberrationIntensity
|
|
258
|
+
}
|
|
259
|
+
glassSize={glassSize}
|
|
260
|
+
padding={padding}
|
|
261
|
+
mouseOffset={effectiveWithoutEffects ? { x: 0, y: 0 } : mouseOffset}
|
|
262
|
+
globalMousePosition={effectiveWithoutEffects ? { x: 0, y: 0 } : globalMousePosition}
|
|
263
|
+
onMouseEnter={handleMouseEnter}
|
|
264
|
+
onMouseLeave={handleMouseLeave}
|
|
265
|
+
onMouseDown={handleMouseDown}
|
|
266
|
+
onMouseUp={handleMouseUp}
|
|
267
|
+
isHovered={isHovered}
|
|
268
|
+
isActive={isActive}
|
|
269
|
+
overLight={overLightConfig?.isOverLight || false}
|
|
270
|
+
onClick={onClick}
|
|
271
|
+
mode={mode}
|
|
272
|
+
transform={baseStyle.transform}
|
|
273
|
+
effectiveWithoutEffects={effectiveWithoutEffects}
|
|
274
|
+
effectiveReducedMotion={effectiveReducedMotion}
|
|
275
|
+
shaderVariant={shaderVariant}
|
|
276
|
+
elasticity={elasticity}
|
|
277
|
+
withLiquidBlur={withLiquidBlur}
|
|
278
|
+
>
|
|
279
|
+
{children}
|
|
280
|
+
</AtomixGlassContainer>
|
|
281
|
+
{withBorder && (
|
|
282
|
+
<>
|
|
283
|
+
<span
|
|
284
|
+
className={ATOMIX_GLASS.BORDER_1_CLASS}
|
|
285
|
+
style={{
|
|
286
|
+
position: `var(--${scopedId}-pos)` as any,
|
|
287
|
+
top: `var(--${scopedId}-top)`,
|
|
288
|
+
left: `var(--${scopedId}-left)`,
|
|
289
|
+
width: `var(--${scopedId}-w)`,
|
|
290
|
+
height: `var(--${scopedId}-h)`,
|
|
291
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
292
|
+
transform: `var(--${scopedId}-t)`,
|
|
293
|
+
transition: `var(--${scopedId}-tr)`,
|
|
294
|
+
background: `var(--${scopedId}-b1)`,
|
|
295
|
+
}}
|
|
296
|
+
/>
|
|
297
|
+
<span
|
|
298
|
+
className={ATOMIX_GLASS.BORDER_2_CLASS}
|
|
299
|
+
style={{
|
|
300
|
+
position: `var(--${scopedId}-pos)` as any,
|
|
301
|
+
top: `var(--${scopedId}-top)`,
|
|
302
|
+
left: `var(--${scopedId}-left)`,
|
|
303
|
+
width: `var(--${scopedId}-w)`,
|
|
304
|
+
height: `var(--${scopedId}-h)`,
|
|
305
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
306
|
+
transform: `var(--${scopedId}-t)`,
|
|
307
|
+
transition: `var(--${scopedId}-tr)`,
|
|
308
|
+
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
309
|
+
background: `var(--${scopedId}-b2)`,
|
|
310
|
+
}}
|
|
311
|
+
/>
|
|
312
|
+
</>
|
|
313
|
+
)}
|
|
314
|
+
{Boolean(onClick) && (
|
|
315
|
+
<>
|
|
316
|
+
<div
|
|
317
|
+
className={ATOMIX_GLASS.HOVER_1_CLASS}
|
|
318
|
+
style={{
|
|
319
|
+
position: 'absolute',
|
|
320
|
+
inset: 0,
|
|
321
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
322
|
+
transform: `var(--${scopedId}-t)`,
|
|
323
|
+
transition: `var(--${scopedId}-tr)`,
|
|
324
|
+
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
325
|
+
opacity: `var(--${scopedId}-h1-o)`,
|
|
326
|
+
background: `var(--${scopedId}-h1)`,
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
<div
|
|
330
|
+
className={ATOMIX_GLASS.HOVER_2_CLASS}
|
|
331
|
+
style={{
|
|
332
|
+
position: 'absolute',
|
|
333
|
+
inset: 0,
|
|
334
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
335
|
+
transform: `var(--${scopedId}-t)`,
|
|
336
|
+
transition: `var(--${scopedId}-tr)`,
|
|
337
|
+
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
338
|
+
opacity: `var(--${scopedId}-h2-o)`,
|
|
339
|
+
background: `var(--${scopedId}-h2)`,
|
|
340
|
+
}}
|
|
341
|
+
/>
|
|
342
|
+
<div
|
|
343
|
+
className={ATOMIX_GLASS.HOVER_3_CLASS}
|
|
344
|
+
style={{
|
|
345
|
+
position: 'absolute',
|
|
346
|
+
inset: 0,
|
|
347
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
348
|
+
transform: `var(--${scopedId}-t)`,
|
|
349
|
+
transition: `var(--${scopedId}-tr)`,
|
|
350
|
+
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
351
|
+
opacity: `var(--${scopedId}-h3-o)`,
|
|
352
|
+
background: `var(--${scopedId}-h3)`,
|
|
353
|
+
}}
|
|
354
|
+
/>
|
|
355
|
+
</>
|
|
356
|
+
)}
|
|
357
|
+
{withOverLightLayers && (
|
|
358
|
+
<>
|
|
359
|
+
<div
|
|
360
|
+
className={ATOMIX_GLASS.BASE_LAYER_CLASS}
|
|
361
|
+
style={{
|
|
362
|
+
position: 'absolute',
|
|
363
|
+
inset: 0,
|
|
364
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
365
|
+
transform: `var(--${scopedId}-t)`,
|
|
366
|
+
transition: `var(--${scopedId}-tr)`,
|
|
367
|
+
opacity: `var(--${scopedId}-base-o)`,
|
|
368
|
+
background: `var(--${scopedId}-base)`,
|
|
369
|
+
}}
|
|
370
|
+
/>
|
|
371
|
+
<div
|
|
372
|
+
className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS}
|
|
373
|
+
style={{
|
|
374
|
+
position: 'absolute',
|
|
375
|
+
inset: 0,
|
|
376
|
+
borderRadius: `var(--${scopedId}-r)`,
|
|
377
|
+
transform: `var(--${scopedId}-t)`,
|
|
378
|
+
transition: `var(--${scopedId}-tr)`,
|
|
379
|
+
opacity: `var(--${scopedId}-over-o)`,
|
|
380
|
+
background: `var(--${scopedId}-over)`,
|
|
381
|
+
}}
|
|
382
|
+
/>
|
|
383
|
+
</>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export default AtomixGlass;
|
|
390
|
+
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { MousePosition } from '../types/components';
|
|
2
2
|
|
|
3
|
+
interface MouseTrackerListener {
|
|
4
|
+
callback: (pos: MousePosition) => void;
|
|
5
|
+
element?: HTMLElement; // Optional element for distance-based attenuation
|
|
6
|
+
maxDistance?: number; // Maximum distance for full effect
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* Global mouse tracker singleton
|
|
5
11
|
* Tracks mouse position at document level and distributes to subscribers
|
|
6
12
|
* Reduces event processing overhead when multiple AtomixGlass instances are present
|
|
7
13
|
*/
|
|
8
14
|
class GlobalMouseTracker {
|
|
9
|
-
private listeners = new Set<
|
|
15
|
+
private listeners = new Set<MouseTrackerListener>();
|
|
10
16
|
private position: MousePosition = { x: 0, y: 0 };
|
|
11
17
|
private rafId: number | null = null;
|
|
12
18
|
private lastEvent: MouseEvent | null = null;
|
|
@@ -15,10 +21,17 @@ class GlobalMouseTracker {
|
|
|
15
21
|
/**
|
|
16
22
|
* Subscribe to mouse position updates
|
|
17
23
|
* @param callback Function to call when mouse position changes
|
|
24
|
+
* @param element Optional element for distance-based attenuation
|
|
25
|
+
* @param maxDistance Optional maximum distance for full effect
|
|
18
26
|
* @returns Unsubscribe function
|
|
19
27
|
*/
|
|
20
|
-
subscribe(
|
|
21
|
-
|
|
28
|
+
subscribe(
|
|
29
|
+
callback: (pos: MousePosition) => void,
|
|
30
|
+
element?: HTMLElement,
|
|
31
|
+
maxDistance?: number
|
|
32
|
+
): () => void {
|
|
33
|
+
const listener: MouseTrackerListener = { callback, element, maxDistance };
|
|
34
|
+
this.listeners.add(listener);
|
|
22
35
|
|
|
23
36
|
// Start tracking if this is the first subscriber
|
|
24
37
|
if (this.listeners.size === 1) {
|
|
@@ -38,7 +51,13 @@ class GlobalMouseTracker {
|
|
|
38
51
|
* Unsubscribe from mouse position updates
|
|
39
52
|
*/
|
|
40
53
|
private unsubscribe(callback: (pos: MousePosition) => void): void {
|
|
41
|
-
|
|
54
|
+
// Find and remove the listener with the given callback
|
|
55
|
+
for (const listener of this.listeners) {
|
|
56
|
+
if (listener.callback === callback) {
|
|
57
|
+
this.listeners.delete(listener);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
42
61
|
|
|
43
62
|
// Stop tracking if no more subscribers
|
|
44
63
|
if (this.listeners.size === 0) {
|
|
@@ -80,6 +99,15 @@ class GlobalMouseTracker {
|
|
|
80
99
|
this.lastEvent = null;
|
|
81
100
|
}
|
|
82
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Calculate distance between two points
|
|
104
|
+
*/
|
|
105
|
+
private calculateDistance(point1: MousePosition, point2: MousePosition): number {
|
|
106
|
+
const dx = point1.x - point2.x;
|
|
107
|
+
const dy = point1.y - point2.y;
|
|
108
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
109
|
+
}
|
|
110
|
+
|
|
83
111
|
/**
|
|
84
112
|
* Handle mouse move event
|
|
85
113
|
*/
|
|
@@ -96,9 +124,37 @@ class GlobalMouseTracker {
|
|
|
96
124
|
};
|
|
97
125
|
|
|
98
126
|
// Notify all subscribers
|
|
99
|
-
this.listeners.forEach(
|
|
127
|
+
this.listeners.forEach(listener => {
|
|
100
128
|
try {
|
|
101
|
-
|
|
129
|
+
// If the listener has an element, calculate distance-based attenuation
|
|
130
|
+
if (listener.element) {
|
|
131
|
+
const elementRect = listener.element.getBoundingClientRect();
|
|
132
|
+
const elementCenter = {
|
|
133
|
+
x: elementRect.left + elementRect.width / 2,
|
|
134
|
+
y: elementRect.top + elementRect.height / 2,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const distance = this.calculateDistance(this.position, elementCenter);
|
|
138
|
+
const maxDistance = listener.maxDistance || 300; // Default to 300px
|
|
139
|
+
|
|
140
|
+
// Calculate attenuation factor (0 to 1)
|
|
141
|
+
const attenuation = Math.max(0, 1 - (distance / maxDistance));
|
|
142
|
+
|
|
143
|
+
// Calculate relative mouse position for this specific element
|
|
144
|
+
const relativeX = (this.position.x - elementCenter.x) / elementRect.width * 100;
|
|
145
|
+
const relativeY = (this.position.y - elementCenter.y) / elementRect.height * 100;
|
|
146
|
+
|
|
147
|
+
// Apply attenuation to the relative position
|
|
148
|
+
const attenuatedRelativePosition = {
|
|
149
|
+
x: relativeX * attenuation,
|
|
150
|
+
y: relativeY * attenuation,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
listener.callback(attenuatedRelativePosition);
|
|
154
|
+
} else {
|
|
155
|
+
// Send original position for listeners without distance-based attenuation
|
|
156
|
+
listener.callback(this.position);
|
|
157
|
+
}
|
|
102
158
|
} catch (error) {
|
|
103
159
|
console.error('GlobalMouseTracker: Error in subscriber callback', error);
|
|
104
160
|
}
|
|
@@ -158,6 +158,7 @@ interface UseAtomixGlassOptions extends Omit<AtomixGlassProps, 'children'> {
|
|
|
158
158
|
wrapperRef?: React.RefObject<HTMLDivElement | null>;
|
|
159
159
|
children?: React.ReactNode;
|
|
160
160
|
isFixedOrSticky?: boolean;
|
|
161
|
+
priority?: number; // Priority for z-index ordering
|
|
161
162
|
// Phase 1: Time-Based Animation System
|
|
162
163
|
withLiquidBlur?: boolean;
|
|
163
164
|
animationQuality?: 'low' | 'medium' | 'high';
|
|
@@ -226,7 +227,7 @@ export function useAtomixGlass({
|
|
|
226
227
|
reducedMotion = false,
|
|
227
228
|
highContrast = false,
|
|
228
229
|
withoutEffects = false,
|
|
229
|
-
elasticity =
|
|
230
|
+
elasticity = ATOMIX_GLASS.DEFAULTS.ELASTICITY,
|
|
230
231
|
onClick,
|
|
231
232
|
debugBorderRadius = false,
|
|
232
233
|
debugOverLight = false,
|
|
@@ -236,6 +237,7 @@ export function useAtomixGlass({
|
|
|
236
237
|
padding,
|
|
237
238
|
withLiquidBlur,
|
|
238
239
|
isFixedOrSticky = false,
|
|
240
|
+
priority = 1, // Default priority
|
|
239
241
|
// Phase 1: Animation System Props
|
|
240
242
|
withTimeAnimation = ATOMIX_GLASS.DEFAULTS.WITH_TIME_ANIMATION,
|
|
241
243
|
animationSpeed = ATOMIX_GLASS.DEFAULTS.ANIMATION_SPEED,
|
|
@@ -965,7 +967,7 @@ export function useAtomixGlass({
|
|
|
965
967
|
return undefined;
|
|
966
968
|
}
|
|
967
969
|
|
|
968
|
-
const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
|
|
970
|
+
const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition, glassRef.current || undefined, 300); // 300px max distance for full effect
|
|
969
971
|
|
|
970
972
|
// Initial start
|
|
971
973
|
startLerpLoop();
|
|
@@ -1689,6 +1689,10 @@ export const ATOMIX_GLASS = {
|
|
|
1689
1689
|
MIN_BLUR: 0.1,
|
|
1690
1690
|
MOUSE_INFLUENCE_DIVISOR: 100,
|
|
1691
1691
|
EDGE_FADE_PIXELS: 2,
|
|
1692
|
+
// Elasticity physics constants
|
|
1693
|
+
ELASTICITY_TRANSLATION_FACTOR: 0.1,
|
|
1694
|
+
ELASTICITY_DISTANCE_THRESHOLD: 200,
|
|
1695
|
+
ELASTICITY_COMPRESSION_FACTOR: 0.3,
|
|
1692
1696
|
// Note: This default must match the SCSS variable --atomix-radius-md
|
|
1693
1697
|
// @see src/styles/01-settings/_settings.global.scss
|
|
1694
1698
|
DEFAULT_CORNER_RADIUS: 16,
|
|
@@ -321,6 +321,16 @@ export interface AtomixGlassProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
321
321
|
* @default false
|
|
322
322
|
*/
|
|
323
323
|
disableResponsiveBreakpoints?: boolean;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Priority level for rendering and performance scheduling
|
|
327
|
+
*
|
|
328
|
+
* Controls the rendering priority of the glass effect, allowing for performance
|
|
329
|
+
* optimization in complex scenes. Higher priority elements are rendered first.
|
|
330
|
+
*
|
|
331
|
+
* @default undefined
|
|
332
|
+
*/
|
|
333
|
+
priority?: number;
|
|
324
334
|
}
|
|
325
335
|
|
|
326
336
|
/**
|
|
@@ -1207,10 +1217,6 @@ export interface SpinnerProps extends BaseComponentProps {
|
|
|
1207
1217
|
glass?: AtomixGlassProps | boolean;
|
|
1208
1218
|
}
|
|
1209
1219
|
|
|
1210
|
-
/**
|
|
1211
|
-
* Icon size options
|
|
1212
|
-
*/
|
|
1213
|
-
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
1214
1220
|
|
|
1215
1221
|
/**
|
|
1216
1222
|
* Icon weight/style options
|