@shohojdhara/atomix 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/atomix.css +1300 -1418
- package/dist/atomix.min.css +3 -3
- package/dist/index.d.ts +1259 -874
- package/dist/index.esm.js +16256 -26366
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +15691 -22295
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/themes/applemix.css +15036 -0
- package/dist/themes/applemix.min.css +72 -0
- package/dist/themes/boomdevs.css +1300 -1419
- package/dist/themes/boomdevs.min.css +3 -3
- package/dist/themes/esrar.css +1301 -1419
- package/dist/themes/esrar.min.css +3 -3
- package/dist/themes/flashtrade.css +15187 -0
- package/dist/themes/flashtrade.min.css +86 -0
- package/dist/themes/mashroom.css +1299 -1417
- package/dist/themes/mashroom.min.css +5 -5
- package/dist/themes/shaj-default.css +1300 -1418
- package/dist/themes/shaj-default.min.css +3 -3
- package/package.json +6 -17
- package/src/components/Accordion/Accordion.stories.tsx +4 -26
- package/src/components/Accordion/Accordion.tsx +21 -12
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +106 -72
- package/src/components/AtomixGlass/AtomixGlass.tsx +485 -1215
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +399 -0
- package/src/components/AtomixGlass/GlassFilter.tsx +156 -0
- package/src/components/AtomixGlass/README.md +124 -2
- package/src/components/AtomixGlass/atomixGLass.old.tsx +1266 -0
- package/src/components/AtomixGlass/glass-utils.ts +263 -0
- package/src/components/AtomixGlass/shader-utils.ts +404 -236
- package/src/components/AtomixGlass/{AtomixGlass.stories.tsx → stories/AtomixGlass.stories.tsx} +55 -35
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +57 -89
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +149 -149
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +95 -32
- package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +0 -2
- package/src/components/AtomixGlass/stories/shared-components.tsx +9 -18
- package/src/components/AtomixGlass/utils.ts +3 -3
- package/src/components/Avatar/Avatar.tsx +3 -0
- package/src/components/Avatar/AvatarGroup.tsx +2 -1
- package/src/components/Badge/Badge.stories.tsx +74 -54
- package/src/components/Badge/Badge.tsx +8 -12
- package/src/components/Breadcrumb/Breadcrumb.tsx +23 -4
- package/src/components/Button/Button.tsx +3 -5
- package/src/components/Callout/Callout.stories.tsx +86 -35
- package/src/components/Callout/Callout.tsx +4 -0
- package/src/components/Card/Card.stories.tsx +89 -85
- package/src/components/Card/Card.tsx +15 -4
- package/src/components/Card/ElevationCard.tsx +2 -0
- package/src/components/Chart/AnimatedChart.tsx +179 -156
- package/src/components/Chart/AreaChart.tsx +123 -12
- package/src/components/Chart/BarChart.tsx +91 -100
- package/src/components/Chart/BaseChart.tsx +80 -0
- package/src/components/Chart/BubbleChart.tsx +114 -290
- package/src/components/Chart/CandlestickChart.tsx +282 -622
- package/src/components/Chart/Chart.stories.tsx +576 -179
- package/src/components/Chart/Chart.tsx +374 -75
- package/src/components/Chart/ChartRenderer.tsx +371 -220
- package/src/components/Chart/ChartToolbar.tsx +372 -61
- package/src/components/Chart/ChartTooltip.tsx +33 -18
- package/src/components/Chart/DonutChart.tsx +172 -254
- package/src/components/Chart/FunnelChart.tsx +169 -240
- package/src/components/Chart/GaugeChart.tsx +224 -392
- package/src/components/Chart/HeatmapChart.tsx +302 -440
- package/src/components/Chart/LineChart.tsx +148 -103
- package/src/components/Chart/MultiAxisChart.tsx +267 -395
- package/src/components/Chart/PieChart.tsx +114 -64
- package/src/components/Chart/RadarChart.tsx +202 -218
- package/src/components/Chart/ScatterChart.tsx +111 -97
- package/src/components/Chart/TreemapChart.tsx +147 -222
- package/src/components/Chart/WaterfallChart.tsx +253 -291
- package/src/components/Chart/index.ts +11 -9
- package/src/components/Chart/types.ts +85 -9
- package/src/components/Chart/utils.ts +66 -0
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +121 -11
- package/src/components/ColorModeToggle/ColorModeToggle.tsx +149 -45
- package/src/components/ColorModeToggle/index.ts +1 -1
- package/src/components/Countdown/Countdown.tsx +4 -0
- package/src/components/DataTable/DataTable.tsx +2 -1
- package/src/components/DatePicker/DatePicker.stories.tsx +0 -11
- package/src/components/DatePicker/DatePicker.tsx +3 -9
- package/src/components/DatePicker/types.ts +5 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +32 -25
- package/src/components/Dropdown/Dropdown.tsx +26 -28
- package/src/components/EdgePanel/EdgePanel.stories.tsx +13 -15
- package/src/components/EdgePanel/EdgePanel.tsx +20 -5
- package/src/components/Footer/Footer.stories.tsx +187 -60
- package/src/components/Footer/Footer.test.tsx +134 -0
- package/src/components/Footer/Footer.tsx +133 -34
- package/src/components/Footer/FooterLink.tsx +1 -1
- package/src/components/Footer/FooterSection.tsx +53 -36
- package/src/components/Footer/FooterSocialLink.tsx +32 -29
- package/src/components/Footer/README.md +82 -3
- package/src/components/Footer/index.ts +1 -1
- package/src/components/Form/Checkbox.stories.tsx +13 -5
- package/src/components/Form/Checkbox.tsx +3 -6
- package/src/components/Form/Form.stories.tsx +10 -3
- package/src/components/Form/Form.tsx +2 -0
- package/src/components/Form/FormGroup.tsx +2 -1
- package/src/components/Form/Input.stories.tsx +12 -11
- package/src/components/Form/Input.tsx +97 -95
- package/src/components/Form/Radio.stories.tsx +22 -7
- package/src/components/Form/Radio.tsx +3 -6
- package/src/components/Form/Select.stories.tsx +21 -6
- package/src/components/Form/Select.tsx +3 -5
- package/src/components/Form/Textarea.stories.tsx +13 -11
- package/src/components/Form/Textarea.tsx +88 -86
- package/src/components/Hero/Hero.stories.tsx +2 -3
- package/src/components/Hero/Hero.tsx +5 -6
- package/src/components/Icon/Icon.tsx +12 -1
- package/src/components/List/List.tsx +2 -1
- package/src/components/List/ListGroup.tsx +2 -1
- package/src/components/Messages/Messages.tsx +3 -2
- package/src/components/Modal/Modal.stories.tsx +48 -34
- package/src/components/Modal/Modal.tsx +19 -23
- package/src/components/Navigation/Menu/MegaMenu.tsx +2 -2
- package/src/components/Navigation/Menu/Menu.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +6 -1
- package/src/components/Navigation/Nav/NavDropdown.tsx +10 -1
- package/src/components/Navigation/Navbar/Navbar.tsx +4 -1
- package/src/components/Navigation/SideMenu/SideMenu.tsx +3 -2
- package/src/components/Pagination/Pagination.stories.tsx +13 -6
- package/src/components/Pagination/Pagination.tsx +7 -6
- package/src/components/PhotoViewer/PhotoViewer.tsx +2 -1
- package/src/components/Popover/Popover.stories.tsx +32 -24
- package/src/components/Popover/Popover.tsx +4 -1
- package/src/components/ProductReview/ProductReview.tsx +8 -2
- package/src/components/Progress/Progress.tsx +2 -1
- package/src/components/Rating/Rating.stories.tsx +11 -6
- package/src/components/Rating/Rating.tsx +3 -5
- package/src/components/River/River.tsx +5 -5
- package/src/components/SectionIntro/SectionIntro.tsx +8 -2
- package/src/components/Slider/Slider.stories.tsx +4 -4
- package/src/components/Slider/Slider.tsx +4 -3
- package/src/components/Spinner/Spinner.tsx +2 -1
- package/src/components/Steps/Steps.stories.tsx +5 -4
- package/src/components/Steps/Steps.tsx +8 -5
- package/src/components/Tab/Tab.stories.tsx +4 -3
- package/src/components/Tab/Tab.tsx +8 -6
- package/src/components/Testimonial/Testimonial.tsx +8 -2
- package/src/components/Todo/Todo.tsx +2 -1
- package/src/components/Toggle/Toggle.stories.tsx +5 -4
- package/src/components/Toggle/Toggle.tsx +8 -5
- package/src/components/Tooltip/Tooltip.stories.tsx +40 -30
- package/src/components/Tooltip/Tooltip.tsx +9 -2
- package/src/components/Upload/Upload.stories.tsx +252 -0
- package/src/components/Upload/Upload.tsx +92 -53
- package/src/components/VideoPlayer/VideoPlayer.tsx +3 -1
- package/src/components/index.ts +0 -4
- package/src/layouts/Grid/Grid.stories.tsx +10 -23
- package/src/layouts/Grid/Grid.tsx +20 -1
- package/src/layouts/Grid/GridCol.tsx +76 -48
- package/src/lib/composables/useAtomixGlass.ts +861 -44
- package/src/lib/composables/useBarChart.ts +13 -6
- package/src/lib/composables/useChart.ts +17 -13
- package/src/lib/composables/useChartExport.ts +19 -78
- package/src/lib/composables/useChartToolbar.ts +0 -1
- package/src/lib/composables/useEdgePanel.ts +111 -103
- package/src/lib/composables/useFooter.ts +3 -3
- package/src/lib/composables/useGlassContainer.ts +16 -7
- package/src/lib/composables/useLineChart.ts +8 -1
- package/src/lib/composables/useRiver.ts +5 -0
- package/src/lib/composables/useSlider.ts +62 -24
- package/src/lib/composables/useVideoPlayer.ts +60 -63
- package/src/lib/constants/components.ts +146 -32
- package/src/lib/types/components.ts +258 -10
- package/src/lib/utils/displacement-generator.ts +55 -49
- package/src/lib/utils/icons.ts +1 -1
- package/src/lib/utils/index.ts +16 -10
- package/src/styles/01-settings/_settings.accordion.scss +19 -19
- package/src/styles/01-settings/_settings.animations.scss +5 -5
- package/src/styles/01-settings/_settings.avatar-group.scss +1 -1
- package/src/styles/01-settings/_settings.avatar.scss +17 -17
- package/src/styles/01-settings/_settings.background.scss +1 -4
- package/src/styles/01-settings/_settings.badge.scss +1 -1
- package/src/styles/01-settings/_settings.breadcrumb.scss +1 -1
- package/src/styles/01-settings/_settings.card.scss +1 -1
- package/src/styles/01-settings/_settings.chart.scss +65 -2
- package/src/styles/01-settings/_settings.dropdown.scss +1 -1
- package/src/styles/01-settings/_settings.footer.scss +35 -42
- package/src/styles/01-settings/_settings.input.scss +1 -1
- package/src/styles/01-settings/_settings.list.scss +1 -1
- package/src/styles/01-settings/_settings.rating.scss +1 -1
- package/src/styles/01-settings/_settings.tabs.scss +1 -1
- package/src/styles/01-settings/_settings.upload.scss +6 -5
- package/src/styles/02-tools/_tools.animations.scss +4 -5
- package/src/styles/02-tools/_tools.background.scss +1 -13
- package/src/styles/02-tools/_tools.glass.scss +0 -1
- package/src/styles/02-tools/_tools.utility-api.scss +42 -34
- package/src/styles/03-generic/_generic.root.scss +1 -4
- package/src/styles/04-elements/_elements.body.scss +0 -1
- package/src/styles/06-components/_components.atomix-glass.scss +217 -39
- package/src/styles/06-components/_components.badge.scss +6 -8
- package/src/styles/06-components/_components.button.scss +8 -3
- package/src/styles/06-components/_components.card.scss +2 -14
- package/src/styles/06-components/_components.chart.scss +969 -1449
- package/src/styles/06-components/_components.color-mode-toggle.scss +43 -6
- package/src/styles/06-components/_components.dropdown.scss +19 -7
- package/src/styles/06-components/_components.edge-panel.scss +4 -2
- package/src/styles/06-components/_components.footer.scss +166 -85
- package/src/styles/06-components/_components.input.scss +8 -9
- package/src/styles/06-components/_components.list.scss +1 -0
- package/src/styles/06-components/_components.modal.scss +5 -3
- package/src/styles/06-components/_components.skeleton.scss +8 -6
- package/src/styles/06-components/_components.upload.scss +219 -4
- package/src/styles/06-components/old.chart.styles.scss +1 -30
- package/src/styles/99-utilities/_utilities.opacity.scss +1 -1
- package/src/styles/99-utilities/_utilities.scss +1 -1
- package/src/components/Chart/AdvancedChart.tsx +0 -624
- package/src/components/Chart/LineChartNew.tsx +0 -167
- package/src/components/Chart/RealTimeChart.tsx +0 -436
- package/src/components/DatePicker/DatePicker copy.tsx +0 -551
|
@@ -1,555 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
useId,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
useMemo,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import {
|
|
12
|
-
ShaderDisplacementGenerator,
|
|
13
|
-
fragmentShaders,
|
|
14
|
-
type FragmentShaderType,
|
|
15
|
-
} from './shader-utils';
|
|
16
|
-
import { displacementMap, polarDisplacementMap, prominentDisplacementMap } from './utils';
|
|
17
|
-
|
|
18
|
-
// Types
|
|
19
|
-
type DisplacementMode = 'standard' | 'polar' | 'prominent' | 'shader';
|
|
20
|
-
type MousePosition = { x: number; y: number };
|
|
21
|
-
type GlassSize = { width: number; height: number };
|
|
22
|
-
type OverLightConfig =
|
|
23
|
-
| boolean
|
|
24
|
-
| 'auto'
|
|
25
|
-
| { threshold?: number; opacity?: number; contrast?: number };
|
|
26
|
-
type OverLightObjectConfig = { threshold?: number; opacity?: number; contrast?: number };
|
|
27
|
-
|
|
28
|
-
// Constants
|
|
29
|
-
const ACTIVATION_ZONE = 200;
|
|
30
|
-
const MIN_BLUR = 0.1;
|
|
31
|
-
const MOUSE_INFLUENCE_DIVISOR = 100;
|
|
32
|
-
const EDGE_FADE_PIXELS = 2;
|
|
33
|
-
|
|
34
|
-
// Helper functions with validation
|
|
35
|
-
const calculateDistance = (pos1: MousePosition, pos2: MousePosition): number => {
|
|
36
|
-
if (
|
|
37
|
-
!pos1 ||
|
|
38
|
-
!pos2 ||
|
|
39
|
-
typeof pos1.x !== 'number' ||
|
|
40
|
-
typeof pos1.y !== 'number' ||
|
|
41
|
-
typeof pos2.x !== 'number' ||
|
|
42
|
-
typeof pos2.y !== 'number'
|
|
43
|
-
) {
|
|
44
|
-
return 0;
|
|
45
|
-
}
|
|
46
|
-
const deltaX = pos1.x - pos2.x;
|
|
47
|
-
const deltaY = pos1.y - pos2.y;
|
|
48
|
-
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const calculateElementCenter = (rect: DOMRect | null): MousePosition => {
|
|
52
|
-
if (!rect) {
|
|
53
|
-
return { x: 0, y: 0 };
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
x: rect.left + rect.width / 2,
|
|
57
|
-
y: rect.top + rect.height / 2,
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const calculateMouseInfluence = (mouseOffset: MousePosition): number => {
|
|
62
|
-
if (!mouseOffset || typeof mouseOffset.x !== 'number' || typeof mouseOffset.y !== 'number') {
|
|
63
|
-
return 0;
|
|
64
|
-
}
|
|
65
|
-
return (
|
|
66
|
-
Math.sqrt(mouseOffset.x * mouseOffset.x + mouseOffset.y * mouseOffset.y) /
|
|
67
|
-
MOUSE_INFLUENCE_DIVISOR
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const clampBlur = (value: number): number => {
|
|
72
|
-
if (typeof value !== 'number' || isNaN(value)) {
|
|
73
|
-
return MIN_BLUR;
|
|
74
|
-
}
|
|
75
|
-
return Math.max(MIN_BLUR, value);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const validateGlassSize = (size: GlassSize): boolean => {
|
|
79
|
-
return (
|
|
80
|
-
size &&
|
|
81
|
-
typeof size.width === 'number' &&
|
|
82
|
-
typeof size.height === 'number' &&
|
|
83
|
-
size.width > 0 &&
|
|
84
|
-
size.height > 0
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const getDisplacementMap = (mode: DisplacementMode, shaderMapUrl?: string): string => {
|
|
90
|
-
switch (mode) {
|
|
91
|
-
case 'standard':
|
|
92
|
-
return displacementMap;
|
|
93
|
-
case 'polar':
|
|
94
|
-
return polarDisplacementMap;
|
|
95
|
-
case 'prominent':
|
|
96
|
-
return prominentDisplacementMap;
|
|
97
|
-
case 'shader':
|
|
98
|
-
return shaderMapUrl || displacementMap;
|
|
99
|
-
default:
|
|
100
|
-
console.warn('AtomixGlass: Invalid displacement mode');
|
|
101
|
-
return displacementMap;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
interface GlassFilterProps {
|
|
106
|
-
id: string;
|
|
107
|
-
displacementScale: number;
|
|
108
|
-
aberrationIntensity: number;
|
|
109
|
-
mode: DisplacementMode;
|
|
110
|
-
shaderMapUrl?: string;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const GlassFilter: React.FC<GlassFilterProps> = ({
|
|
114
|
-
id,
|
|
115
|
-
displacementScale,
|
|
116
|
-
aberrationIntensity,
|
|
117
|
-
mode,
|
|
118
|
-
shaderMapUrl,
|
|
119
|
-
}) => (
|
|
120
|
-
<svg
|
|
121
|
-
style={{
|
|
122
|
-
position: 'absolute',
|
|
123
|
-
width: '100%',
|
|
124
|
-
height: '100%',
|
|
125
|
-
inset: 0,
|
|
126
|
-
visibility: 'hidden',
|
|
127
|
-
opacity: 0,
|
|
128
|
-
}}
|
|
129
|
-
aria-hidden="true"
|
|
130
|
-
>
|
|
131
|
-
<defs>
|
|
132
|
-
<radialGradient id={`${id}-edge-mask`} cx="50%" cy="50%" r="50%">
|
|
133
|
-
<stop offset="0%" stopColor="black" stopOpacity="0" />
|
|
134
|
-
<stop
|
|
135
|
-
offset={`${Math.max(30, 80 - aberrationIntensity * 2)}%`}
|
|
136
|
-
stopColor="black"
|
|
137
|
-
stopOpacity="0"
|
|
138
|
-
/>
|
|
139
|
-
<stop offset="100%" stopColor="white" stopOpacity="1" />
|
|
140
|
-
</radialGradient>
|
|
141
|
-
<filter id={id} x="-35%" y="-35%" width="170%" height="170%" colorInterpolationFilters="sRGB">
|
|
142
|
-
<feImage
|
|
143
|
-
id="feimage"
|
|
144
|
-
x="0"
|
|
145
|
-
y="0"
|
|
146
|
-
width="100%"
|
|
147
|
-
height="100%"
|
|
148
|
-
result="DISPLACEMENT_MAP"
|
|
149
|
-
href={getDisplacementMap(mode, shaderMapUrl)}
|
|
150
|
-
preserveAspectRatio="xMidYMid slice"
|
|
151
|
-
/>
|
|
152
|
-
|
|
153
|
-
<feColorMatrix
|
|
154
|
-
in="DISPLACEMENT_MAP"
|
|
155
|
-
type="matrix"
|
|
156
|
-
values="0.3 0.3 0.3 0 0
|
|
157
|
-
0.3 0.3 0.3 0 0
|
|
158
|
-
0.3 0.3 0.3 0 0
|
|
159
|
-
0 0 0 1 0"
|
|
160
|
-
result="EDGE_INTENSITY"
|
|
161
|
-
/>
|
|
162
|
-
<feComponentTransfer in="EDGE_INTENSITY" result="EDGE_MASK">
|
|
163
|
-
<feFuncA type="discrete" tableValues={`0 ${aberrationIntensity * 0.05} 1`} />
|
|
164
|
-
</feComponentTransfer>
|
|
165
|
-
|
|
166
|
-
<feOffset in="SourceGraphic" dx="0" dy="0" result="CENTER_ORIGINAL" />
|
|
167
|
-
|
|
168
|
-
<feDisplacementMap
|
|
169
|
-
in="SourceGraphic"
|
|
170
|
-
in2="DISPLACEMENT_MAP"
|
|
171
|
-
scale={displacementScale * (mode === 'shader' ? 1 : -1)}
|
|
172
|
-
xChannelSelector="R"
|
|
173
|
-
yChannelSelector="B"
|
|
174
|
-
result="RED_DISPLACED"
|
|
175
|
-
/>
|
|
176
|
-
<feColorMatrix
|
|
177
|
-
in="RED_DISPLACED"
|
|
178
|
-
type="matrix"
|
|
179
|
-
values="1 0 0 0 0
|
|
180
|
-
0 0 0 0 0
|
|
181
|
-
0 0 0 0 0
|
|
182
|
-
0 0 0 1 0"
|
|
183
|
-
result="RED_CHANNEL"
|
|
184
|
-
/>
|
|
185
|
-
|
|
186
|
-
<feDisplacementMap
|
|
187
|
-
in="SourceGraphic"
|
|
188
|
-
in2="DISPLACEMENT_MAP"
|
|
189
|
-
scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.02)}
|
|
190
|
-
xChannelSelector="R"
|
|
191
|
-
yChannelSelector="B"
|
|
192
|
-
result="GREEN_DISPLACED"
|
|
193
|
-
/>
|
|
194
|
-
<feColorMatrix
|
|
195
|
-
in="GREEN_DISPLACED"
|
|
196
|
-
type="matrix"
|
|
197
|
-
values="0 0 0 0 0
|
|
198
|
-
0 1 0 0 0
|
|
199
|
-
0 0 0 0 0
|
|
200
|
-
0 0 0 1 0"
|
|
201
|
-
result="GREEN_CHANNEL"
|
|
202
|
-
/>
|
|
203
|
-
|
|
204
|
-
<feDisplacementMap
|
|
205
|
-
in="SourceGraphic"
|
|
206
|
-
in2="DISPLACEMENT_MAP"
|
|
207
|
-
scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.03)}
|
|
208
|
-
xChannelSelector="R"
|
|
209
|
-
yChannelSelector="B"
|
|
210
|
-
result="BLUE_DISPLACED"
|
|
211
|
-
/>
|
|
212
|
-
<feColorMatrix
|
|
213
|
-
in="BLUE_DISPLACED"
|
|
214
|
-
type="matrix"
|
|
215
|
-
values="0 0 0 0 0
|
|
216
|
-
0 0 0 0 0
|
|
217
|
-
0 0 1 0 0
|
|
218
|
-
0 0 0 1 0"
|
|
219
|
-
result="BLUE_CHANNEL"
|
|
220
|
-
/>
|
|
221
|
-
|
|
222
|
-
<feBlend in="GREEN_CHANNEL" in2="BLUE_CHANNEL" mode="screen" result="GB_COMBINED" />
|
|
223
|
-
<feBlend in="RED_CHANNEL" in2="GB_COMBINED" mode="screen" result="RGB_COMBINED" />
|
|
224
|
-
|
|
225
|
-
<feGaussianBlur
|
|
226
|
-
in="RGB_COMBINED"
|
|
227
|
-
stdDeviation={Math.max(0.1, 0.5 - aberrationIntensity * 0.1)}
|
|
228
|
-
result="ABERRATED_BLURRED"
|
|
229
|
-
/>
|
|
230
|
-
|
|
231
|
-
<feComposite
|
|
232
|
-
in="ABERRATED_BLURRED"
|
|
233
|
-
in2="EDGE_MASK"
|
|
234
|
-
operator="in"
|
|
235
|
-
result="EDGE_ABERRATION"
|
|
236
|
-
/>
|
|
237
|
-
|
|
238
|
-
<feComponentTransfer in="EDGE_MASK" result="INVERTED_MASK">
|
|
239
|
-
<feFuncA type="table" tableValues="1 0" />
|
|
240
|
-
</feComponentTransfer>
|
|
241
|
-
<feComposite in="CENTER_ORIGINAL" in2="INVERTED_MASK" operator="in" result="CENTER_CLEAN" />
|
|
242
|
-
|
|
243
|
-
<feComposite in="EDGE_ABERRATION" in2="CENTER_CLEAN" operator="over" />
|
|
244
|
-
</filter>
|
|
245
|
-
</defs>
|
|
246
|
-
</svg>
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
interface GlassContainerProps {
|
|
250
|
-
className?: string;
|
|
251
|
-
style?: React.CSSProperties;
|
|
252
|
-
displacementScale?: number;
|
|
253
|
-
blurAmount?: number;
|
|
254
|
-
saturation?: number;
|
|
255
|
-
aberrationIntensity?: number;
|
|
256
|
-
mouseOffset?: MousePosition;
|
|
257
|
-
globalMousePosition?: MousePosition;
|
|
258
|
-
onMouseLeave?: () => void;
|
|
259
|
-
onMouseEnter?: () => void;
|
|
260
|
-
onMouseDown?: () => void;
|
|
261
|
-
onMouseUp?: () => void;
|
|
262
|
-
active?: boolean;
|
|
263
|
-
isHovered?: boolean;
|
|
264
|
-
isActive?: boolean;
|
|
265
|
-
overLight?: boolean;
|
|
266
|
-
cornerRadius?: number;
|
|
267
|
-
padding?: string;
|
|
268
|
-
glassSize?: GlassSize;
|
|
269
|
-
onClick?: () => void;
|
|
270
|
-
mode?: DisplacementMode;
|
|
271
|
-
transform?: string;
|
|
272
|
-
effectiveDisableEffects?: boolean;
|
|
273
|
-
effectiveReducedMotion?: boolean;
|
|
274
|
-
shaderVariant?: FragmentShaderType;
|
|
275
|
-
enableLiquidBlur?: boolean;
|
|
276
|
-
elasticity?: number;
|
|
277
|
-
children?: React.ReactNode;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const GlassContainer = forwardRef<HTMLDivElement, GlassContainerProps>(
|
|
281
|
-
(
|
|
282
|
-
{
|
|
283
|
-
children,
|
|
284
|
-
className = '',
|
|
285
|
-
style,
|
|
286
|
-
displacementScale = 25,
|
|
287
|
-
blurAmount = 0.0625,
|
|
288
|
-
saturation = 180,
|
|
289
|
-
aberrationIntensity = 2,
|
|
290
|
-
mouseOffset = { x: 0, y: 0 },
|
|
291
|
-
globalMousePosition = { x: 0, y: 0 },
|
|
292
|
-
onMouseEnter,
|
|
293
|
-
onMouseLeave,
|
|
294
|
-
onMouseDown,
|
|
295
|
-
onMouseUp,
|
|
296
|
-
active = false,
|
|
297
|
-
isHovered = false,
|
|
298
|
-
isActive = false,
|
|
299
|
-
overLight = false,
|
|
300
|
-
cornerRadius = 0,
|
|
301
|
-
padding = '0 0',
|
|
302
|
-
glassSize = { width: 0, height: 0 },
|
|
303
|
-
onClick,
|
|
304
|
-
mode = 'standard',
|
|
305
|
-
transform = 'none',
|
|
306
|
-
effectiveDisableEffects = false,
|
|
307
|
-
effectiveReducedMotion = false,
|
|
308
|
-
shaderVariant = 'liquidGlass',
|
|
309
|
-
enableLiquidBlur = false,
|
|
310
|
-
elasticity = 0,
|
|
311
|
-
},
|
|
312
|
-
ref
|
|
313
|
-
) => {
|
|
314
|
-
const filterId = useId();
|
|
315
|
-
const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
|
|
316
|
-
const shaderGeneratorRef = useRef<ShaderDisplacementGenerator | null>(null);
|
|
317
|
-
|
|
318
|
-
// Generate initial shader map when mode/size/variant changes
|
|
319
|
-
useEffect(() => {
|
|
320
|
-
if (mode === 'shader' && glassSize.width > 0 && glassSize.height > 0) {
|
|
321
|
-
shaderGeneratorRef.current?.destroy();
|
|
322
|
-
const selectedShader = fragmentShaders[shaderVariant] || fragmentShaders.liquidGlass;
|
|
323
|
-
shaderGeneratorRef.current = new ShaderDisplacementGenerator({
|
|
324
|
-
width: glassSize.width,
|
|
325
|
-
height: glassSize.height,
|
|
326
|
-
fragment: selectedShader,
|
|
327
|
-
});
|
|
328
|
-
const url = shaderGeneratorRef.current.updateShader();
|
|
329
|
-
setShaderMapUrl(url);
|
|
330
|
-
}
|
|
331
|
-
return () => {
|
|
332
|
-
shaderGeneratorRef.current?.destroy();
|
|
333
|
-
shaderGeneratorRef.current = null;
|
|
334
|
-
};
|
|
335
|
-
}, [mode, glassSize.width, glassSize.height, shaderVariant]);
|
|
336
|
-
|
|
337
|
-
useEffect(() => {
|
|
338
|
-
if (!ref || typeof ref === 'function') return;
|
|
339
|
-
|
|
340
|
-
const element = (ref as React.RefObject<HTMLDivElement>).current;
|
|
341
|
-
if (!element) return;
|
|
342
|
-
|
|
343
|
-
const timeoutId = setTimeout(() => {
|
|
344
|
-
// Force reflow to ensure proper sizing
|
|
345
|
-
element.offsetHeight;
|
|
346
|
-
}, 0);
|
|
347
|
-
|
|
348
|
-
return () => clearTimeout(timeoutId);
|
|
349
|
-
}, [cornerRadius, glassSize.width, glassSize.height]);
|
|
350
|
-
|
|
351
|
-
const [rectCache, setRectCache] = useState<DOMRect | null>(null);
|
|
352
|
-
|
|
353
|
-
useEffect(() => {
|
|
354
|
-
if (!ref || typeof ref === 'function') return;
|
|
355
|
-
const element = (ref as React.RefObject<HTMLDivElement>).current;
|
|
356
|
-
if (!element) return;
|
|
357
|
-
setRectCache(element.getBoundingClientRect());
|
|
358
|
-
}, [ref, glassSize]);
|
|
359
|
-
|
|
360
|
-
const liquidBlur = useMemo(() => {
|
|
361
|
-
const defaultBlur = {
|
|
362
|
-
baseBlur: blurAmount,
|
|
363
|
-
edgeBlur: blurAmount * 1.25,
|
|
364
|
-
centerBlur: blurAmount * 1.1,
|
|
365
|
-
flowBlur: blurAmount * 1.2,
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
if (!enableLiquidBlur || !rectCache || !globalMousePosition.x || !globalMousePosition.y) {
|
|
369
|
-
return defaultBlur;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const center = calculateElementCenter(rectCache);
|
|
373
|
-
const distance = calculateDistance(globalMousePosition, center);
|
|
374
|
-
const maxDistance =
|
|
375
|
-
Math.sqrt(rectCache.width * rectCache.width + rectCache.height * rectCache.height) / 2;
|
|
376
|
-
const normalizedDistance = Math.min(distance / maxDistance, 1);
|
|
377
|
-
const mouseInfluence = calculateMouseInfluence(mouseOffset);
|
|
378
|
-
|
|
379
|
-
const baseBlur = blurAmount + mouseInfluence * blurAmount * 0.4;
|
|
380
|
-
const edgeIntensity = normalizedDistance * 1.5 + mouseInfluence * 0.3;
|
|
381
|
-
const edgeBlur = baseBlur * (0.8 + edgeIntensity * 0.6);
|
|
382
|
-
const centerIntensity = (1 - normalizedDistance) * 0.3 + mouseInfluence * 0.2;
|
|
383
|
-
const centerBlur = baseBlur * (0.3 + centerIntensity * 0.4);
|
|
384
|
-
const deltaX = globalMousePosition.x - center.x;
|
|
385
|
-
const deltaY = globalMousePosition.y - center.y;
|
|
386
|
-
const flowDirection = Math.atan2(deltaY, deltaX);
|
|
387
|
-
const flowIntensity = Math.sin(flowDirection + mouseInfluence * Math.PI) * 0.5 + 0.5;
|
|
388
|
-
const flowBlur = baseBlur * (0.4 + flowIntensity * 0.6);
|
|
389
|
-
|
|
390
|
-
const hoverMultiplier = isHovered ? 1.2 : 1;
|
|
391
|
-
const activeMultiplier = isActive ? 1.4 : 1;
|
|
392
|
-
const stateMultiplier = hoverMultiplier * activeMultiplier;
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
baseBlur: clampBlur(baseBlur * stateMultiplier),
|
|
396
|
-
edgeBlur: clampBlur(edgeBlur * stateMultiplier),
|
|
397
|
-
centerBlur: clampBlur(centerBlur * stateMultiplier),
|
|
398
|
-
flowBlur: clampBlur(flowBlur * stateMultiplier),
|
|
399
|
-
};
|
|
400
|
-
}, [enableLiquidBlur, blurAmount, globalMousePosition, mouseOffset, isHovered, isActive, rectCache]);
|
|
401
|
-
|
|
402
|
-
const backdropStyle = useMemo(() => {
|
|
403
|
-
const dynamicSaturation = saturation + liquidBlur.baseBlur * 20;
|
|
404
|
-
|
|
405
|
-
const blurLayers = [
|
|
406
|
-
`blur(${liquidBlur.baseBlur}px)`,
|
|
407
|
-
`blur(${liquidBlur.edgeBlur}px)`,
|
|
408
|
-
`blur(${liquidBlur.centerBlur}px)`,
|
|
409
|
-
`blur(${liquidBlur.flowBlur}px)`,
|
|
410
|
-
];
|
|
411
|
-
|
|
412
|
-
return {
|
|
413
|
-
backdropFilter: `${blurLayers.join(' ')} saturate(${Math.min(dynamicSaturation, 200)}%) url(#${filterId})`,
|
|
414
|
-
};
|
|
415
|
-
}, [filterId, liquidBlur, saturation]);
|
|
416
|
-
|
|
417
|
-
const containerVars = useMemo(() => {
|
|
418
|
-
const mx = mouseOffset?.x || 0;
|
|
419
|
-
const my = mouseOffset?.y || 0;
|
|
420
|
-
const scopedId = `gc-${filterId.replace(/:/g, '')}`;
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
[`--${scopedId}-padding`]: padding,
|
|
424
|
-
[`--${scopedId}-radius`]: `${cornerRadius}px`,
|
|
425
|
-
[`--${scopedId}-backdrop`]: backdropStyle.backdropFilter,
|
|
426
|
-
[`--${scopedId}-shadow`]: overLight
|
|
427
|
-
? [
|
|
428
|
-
`inset 0 1px 0 rgba(255, 255, 255, ${0.4 + mx * 0.002})`,
|
|
429
|
-
`inset 0 -1px 0 rgba(0, 0, 0, ${0.2 + Math.abs(my) * 0.001})`,
|
|
430
|
-
`inset 0 0 20px rgba(0, 0, 0, ${0.08 + Math.abs(mx + my) * 0.001})`,
|
|
431
|
-
`0 2px 12px rgba(0, 0, 0, ${0.12 + Math.abs(my) * 0.002})`,
|
|
432
|
-
].join(', ')
|
|
433
|
-
: '0 0 20px rgba(0, 0, 0, 0.15) inset, 0 4px 8px rgba(0, 0, 0, 0.08) inset',
|
|
434
|
-
[`--${scopedId}-shadow-opacity`]: effectiveDisableEffects ? 0 : 1,
|
|
435
|
-
[`--${scopedId}-bg`]: overLight
|
|
436
|
-
? `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%)`
|
|
437
|
-
: 'none',
|
|
438
|
-
[`--${scopedId}-text-shadow`]: overLight
|
|
439
|
-
? '0px 1px 3px rgba(0, 0, 0, 0.2), 0px 2px 8px rgba(0, 0, 0, 0.1)'
|
|
440
|
-
: '0px 2px 12px rgba(0, 0, 0, 0.4)',
|
|
441
|
-
'--gc-scoped-id': scopedId,
|
|
442
|
-
} as React.CSSProperties;
|
|
443
|
-
}, [filterId, padding, cornerRadius, backdropStyle, mouseOffset, overLight, effectiveDisableEffects]);
|
|
444
|
-
|
|
445
|
-
const scopedId = `gc-${filterId.replace(/:/g, '')}`;
|
|
446
|
-
|
|
447
|
-
return (
|
|
448
|
-
<div
|
|
449
|
-
ref={ref}
|
|
450
|
-
className={` ${className} ${active ? 'active' : ''}`}
|
|
451
|
-
style={{ ...style, ...containerVars }}
|
|
452
|
-
onClick={onClick}
|
|
453
|
-
>
|
|
454
|
-
<div
|
|
455
|
-
className="atomix-glass"
|
|
456
|
-
style={{
|
|
457
|
-
position: 'relative',
|
|
458
|
-
padding: `var(--${scopedId}-padding)`,
|
|
459
|
-
borderRadius: `var(--${scopedId}-radius)`,
|
|
460
|
-
}}
|
|
461
|
-
onMouseEnter={onMouseEnter}
|
|
462
|
-
onMouseLeave={onMouseLeave}
|
|
463
|
-
onMouseDown={onMouseDown}
|
|
464
|
-
onMouseUp={onMouseUp}
|
|
465
|
-
>
|
|
466
|
-
<GlassFilter
|
|
467
|
-
mode={mode}
|
|
468
|
-
id={filterId}
|
|
469
|
-
displacementScale={displacementScale}
|
|
470
|
-
aberrationIntensity={aberrationIntensity}
|
|
471
|
-
shaderMapUrl={shaderMapUrl}
|
|
472
|
-
/>
|
|
473
|
-
<span
|
|
474
|
-
className="atomix-glass__warp"
|
|
475
|
-
style={{
|
|
476
|
-
backdropFilter: `var(--${scopedId}-backdrop)`,
|
|
477
|
-
borderRadius: `var(--${scopedId}-radius)`,
|
|
478
|
-
position: 'absolute',
|
|
479
|
-
inset: '0',
|
|
480
|
-
}}
|
|
481
|
-
/>
|
|
482
|
-
|
|
483
|
-
{/* Enhanced Apple Liquid Glass Inner Shadow Layer */}
|
|
484
|
-
<div
|
|
485
|
-
style={{
|
|
486
|
-
position: 'absolute',
|
|
487
|
-
inset: '1.5px',
|
|
488
|
-
borderRadius: `var(--${scopedId}-radius)`,
|
|
489
|
-
pointerEvents: 'none',
|
|
490
|
-
boxShadow: `var(--${scopedId}-shadow)`,
|
|
491
|
-
opacity: `var(--${scopedId}-shadow-opacity)`,
|
|
492
|
-
background: `var(--${scopedId}-bg)`,
|
|
493
|
-
}}
|
|
494
|
-
/>
|
|
495
|
-
|
|
496
|
-
<div
|
|
497
|
-
style={{
|
|
498
|
-
position: 'relative',
|
|
499
|
-
...(elasticity !== 0 && {
|
|
500
|
-
zIndex: 4,
|
|
501
|
-
textShadow: `var(--${scopedId}-text-shadow)`,
|
|
502
|
-
}),
|
|
503
|
-
}}
|
|
504
|
-
>
|
|
505
|
-
{children}
|
|
506
|
-
</div>
|
|
507
|
-
</div>
|
|
508
|
-
</div>
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
GlassContainer.displayName = 'GlassContainer';
|
|
514
|
-
|
|
515
|
-
interface AtomixGlassProps {
|
|
516
|
-
children: React.ReactNode;
|
|
517
|
-
displacementScale?: number;
|
|
518
|
-
blurAmount?: number;
|
|
519
|
-
saturation?: number;
|
|
520
|
-
aberrationIntensity?: number;
|
|
521
|
-
elasticity?: number;
|
|
522
|
-
cornerRadius?: number;
|
|
523
|
-
globalMousePosition?: MousePosition;
|
|
524
|
-
mouseOffset?: MousePosition;
|
|
525
|
-
mouseContainer?: React.RefObject<HTMLElement | null> | null;
|
|
526
|
-
className?: string;
|
|
527
|
-
padding?: string;
|
|
528
|
-
style?: React.CSSProperties;
|
|
529
|
-
overLight?: OverLightConfig;
|
|
530
|
-
mode?: DisplacementMode;
|
|
531
|
-
onClick?: () => void;
|
|
532
|
-
|
|
533
|
-
// Shader variant selection
|
|
534
|
-
shaderVariant?: FragmentShaderType;
|
|
535
|
-
|
|
536
|
-
// Accessibility props
|
|
537
|
-
'aria-label'?: string;
|
|
538
|
-
'aria-describedby'?: string;
|
|
539
|
-
role?: string;
|
|
540
|
-
tabIndex?: number;
|
|
541
|
-
|
|
542
|
-
// Performance and accessibility options
|
|
543
|
-
reducedMotion?: boolean;
|
|
544
|
-
highContrast?: boolean;
|
|
545
|
-
disableEffects?: boolean;
|
|
546
|
-
enableLiquidBlur?: boolean;
|
|
547
|
-
enableBorderEffect?: boolean;
|
|
548
|
-
enableOverLightLayers?: boolean;
|
|
549
|
-
|
|
550
|
-
// Performance monitoring
|
|
551
|
-
enablePerformanceMonitoring?: boolean;
|
|
552
|
-
}
|
|
1
|
+
import React, { useMemo, useRef } from 'react';
|
|
2
|
+
import type { AtomixGlassProps, GlassSize } from '../../lib/types/components';
|
|
3
|
+
import { ATOMIX_GLASS } from '../../lib/constants/components';
|
|
4
|
+
import { AtomixGlassContainer } from './AtomixGlassContainer';
|
|
5
|
+
import { useAtomixGlass } from '../../lib/composables/useAtomixGlass';
|
|
553
6
|
|
|
554
7
|
/**
|
|
555
8
|
* AtomixGlass - A high-performance glass morphism component with liquid distortion effects
|
|
@@ -557,647 +10,496 @@ interface AtomixGlassProps {
|
|
|
557
10
|
* Features:
|
|
558
11
|
* - Hardware-accelerated glass effects with SVG filters
|
|
559
12
|
* - Mouse-responsive liquid distortion
|
|
560
|
-
* -
|
|
13
|
+
* - Dynamic border-radius extraction from children CSS properties
|
|
14
|
+
* - Automatic light/dark theme detection via overLight prop
|
|
561
15
|
* - Accessibility and performance optimizations
|
|
562
16
|
* - Multiple displacement modes (standard, polar, prominent, shader)
|
|
17
|
+
* - Design token integration for consistent theming
|
|
18
|
+
* - Focus ring support for keyboard navigation
|
|
19
|
+
* - Responsive breakpoints for mobile optimization
|
|
20
|
+
* - Enhanced ARIA attributes for screen readers
|
|
21
|
+
*
|
|
22
|
+
* Design System Compliance:
|
|
23
|
+
* - Uses design tokens for opacity, spacing, and colors
|
|
24
|
+
* - Follows BEM methodology for class naming
|
|
25
|
+
* - Implements focus-ring mixin for accessibility
|
|
26
|
+
* - Supports reduced motion and high contrast preferences
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // Basic usage with dynamic border-radius extraction
|
|
30
|
+
* <AtomixGlass>
|
|
31
|
+
* <div style={{ borderRadius: '12px' }}>Content with 12px radius</div>
|
|
32
|
+
* </AtomixGlass>
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Manual border-radius override
|
|
36
|
+
* <AtomixGlass cornerRadius={20}>
|
|
37
|
+
* <div>Content with 20px glass radius</div>
|
|
38
|
+
* </AtomixGlass>
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Interactive glass with click handler
|
|
42
|
+
* <AtomixGlass onClick={() => console.log('Clicked')} aria-label="Glass card">
|
|
43
|
+
* <div>Clickable content</div>
|
|
44
|
+
* </AtomixGlass>
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // OverLight - Boolean mode (explicit control)
|
|
48
|
+
* <AtomixGlass overLight={true}>
|
|
49
|
+
* <div>Content on light background</div>
|
|
50
|
+
* </AtomixGlass>
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // OverLight - Auto-detection mode
|
|
54
|
+
* <AtomixGlass overLight="auto">
|
|
55
|
+
* <div>Content with auto-detected background</div>
|
|
56
|
+
* </AtomixGlass>
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // OverLight - Object config with custom settings
|
|
60
|
+
* <AtomixGlass
|
|
61
|
+
* overLight={{
|
|
62
|
+
* threshold: 0.8,
|
|
63
|
+
* opacity: 0.6,
|
|
64
|
+
* contrast: 1.8,
|
|
65
|
+
* brightness: 1.0,
|
|
66
|
+
* saturationBoost: 1.5
|
|
67
|
+
* }}
|
|
68
|
+
* >
|
|
69
|
+
* <div>Content with custom overLight config</div>
|
|
70
|
+
* </AtomixGlass>
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Debug mode for overLight detection
|
|
74
|
+
* <AtomixGlass overLight="auto" debugOverLight={true}>
|
|
75
|
+
* <div>Content with debug logging enabled</div>
|
|
76
|
+
* </AtomixGlass>
|
|
563
77
|
*/
|
|
564
78
|
export function AtomixGlass({
|
|
565
79
|
children,
|
|
566
|
-
displacementScale =
|
|
567
|
-
blurAmount =
|
|
568
|
-
saturation =
|
|
569
|
-
aberrationIntensity =
|
|
570
|
-
elasticity =
|
|
571
|
-
cornerRadius
|
|
80
|
+
displacementScale = ATOMIX_GLASS.DEFAULTS.DISPLACEMENT_SCALE,
|
|
81
|
+
blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT,
|
|
82
|
+
saturation = ATOMIX_GLASS.DEFAULTS.SATURATION,
|
|
83
|
+
aberrationIntensity = ATOMIX_GLASS.DEFAULTS.ABERRATION_INTENSITY,
|
|
84
|
+
elasticity = ATOMIX_GLASS.DEFAULTS.ELASTICITY,
|
|
85
|
+
cornerRadius,
|
|
572
86
|
globalMousePosition: externalGlobalMousePosition,
|
|
573
87
|
mouseOffset: externalMouseOffset,
|
|
574
88
|
mouseContainer = null,
|
|
575
89
|
className = '',
|
|
576
|
-
padding =
|
|
577
|
-
overLight =
|
|
90
|
+
padding = ATOMIX_GLASS.DEFAULTS.PADDING,
|
|
91
|
+
overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
|
|
578
92
|
style = {},
|
|
579
|
-
mode =
|
|
93
|
+
mode = ATOMIX_GLASS.DEFAULTS.MODE,
|
|
580
94
|
onClick,
|
|
581
95
|
shaderVariant = 'liquidGlass',
|
|
582
96
|
'aria-label': ariaLabel,
|
|
583
97
|
'aria-describedby': ariaDescribedBy,
|
|
584
98
|
role,
|
|
585
99
|
tabIndex,
|
|
586
|
-
|
|
587
100
|
reducedMotion = false,
|
|
588
101
|
highContrast = false,
|
|
589
102
|
disableEffects = false,
|
|
590
103
|
enableLiquidBlur = false,
|
|
591
104
|
enableBorderEffect = true,
|
|
592
|
-
enableOverLightLayers =
|
|
593
|
-
|
|
105
|
+
enableOverLightLayers = ATOMIX_GLASS.DEFAULTS.ENABLE_OVER_LIGHT_LAYERS,
|
|
594
106
|
enablePerformanceMonitoring = false,
|
|
107
|
+
debugCornerRadius = false,
|
|
108
|
+
debugOverLight = false,
|
|
595
109
|
}: AtomixGlassProps) {
|
|
596
110
|
const glassRef = useRef<HTMLDivElement>(null);
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
111
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
112
|
+
|
|
113
|
+
// Use composable hook for all state and logic
|
|
114
|
+
const {
|
|
115
|
+
isHovered,
|
|
116
|
+
isActive,
|
|
117
|
+
glassSize,
|
|
118
|
+
effectiveCornerRadius,
|
|
119
|
+
effectiveReducedMotion,
|
|
120
|
+
effectiveHighContrast,
|
|
121
|
+
effectiveDisableEffects,
|
|
122
|
+
overLightConfig,
|
|
123
|
+
globalMousePosition,
|
|
124
|
+
mouseOffset,
|
|
125
|
+
transformStyle,
|
|
126
|
+
handleMouseEnter,
|
|
127
|
+
handleMouseLeave,
|
|
128
|
+
handleMouseDown,
|
|
129
|
+
handleMouseUp,
|
|
130
|
+
handleKeyDown,
|
|
131
|
+
} = useAtomixGlass({
|
|
132
|
+
glassRef,
|
|
133
|
+
contentRef,
|
|
134
|
+
cornerRadius,
|
|
135
|
+
globalMousePosition: externalGlobalMousePosition,
|
|
136
|
+
mouseOffset: externalMouseOffset,
|
|
137
|
+
mouseContainer,
|
|
138
|
+
overLight,
|
|
139
|
+
reducedMotion,
|
|
140
|
+
highContrast,
|
|
141
|
+
disableEffects,
|
|
142
|
+
elasticity,
|
|
143
|
+
onClick,
|
|
144
|
+
debugCornerRadius,
|
|
145
|
+
debugOverLight,
|
|
146
|
+
enablePerformanceMonitoring,
|
|
147
|
+
children,
|
|
603
148
|
});
|
|
604
|
-
const [internalMouseOffset, setInternalMouseOffset] = useState<MousePosition>({ x: 0, y: 0 });
|
|
605
149
|
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
const
|
|
150
|
+
// Use consistent overLight state from hook
|
|
151
|
+
const isOverLight = overLightConfig.isOverLight;
|
|
152
|
+
const shouldRenderOverLightLayers = enableOverLightLayers && isOverLight;
|
|
609
153
|
|
|
610
|
-
//
|
|
611
|
-
const
|
|
612
|
-
() =>
|
|
613
|
-
[
|
|
614
|
-
);
|
|
615
|
-
const effectiveHighContrast = useMemo(
|
|
616
|
-
() => highContrast || userPrefersHighContrast,
|
|
617
|
-
[highContrast, userPrefersHighContrast]
|
|
154
|
+
// Memoize transition duration using design token pattern
|
|
155
|
+
const transitionDuration = useMemo(
|
|
156
|
+
() => (effectiveReducedMotion ? 'none' : 'var(--atomix-transition-duration, 0.2s) ease-out'),
|
|
157
|
+
[effectiveReducedMotion]
|
|
618
158
|
);
|
|
619
|
-
const effectiveDisableEffects = useMemo(
|
|
620
|
-
() => disableEffects || effectiveReducedMotion,
|
|
621
|
-
[disableEffects, effectiveReducedMotion]
|
|
622
|
-
);
|
|
623
|
-
|
|
624
|
-
useEffect(() => {
|
|
625
|
-
// Enhanced auto-detect light background with multiple sampling points
|
|
626
|
-
if (overLight === 'auto' && glassRef.current) {
|
|
627
|
-
try {
|
|
628
|
-
const element = glassRef.current;
|
|
629
|
-
const rect = element.getBoundingClientRect();
|
|
630
|
-
|
|
631
|
-
let totalLuminance = 0;
|
|
632
|
-
let validSamples = 0;
|
|
633
|
-
|
|
634
|
-
// Check parent elements and computed styles
|
|
635
|
-
let currentElement = element.parentElement;
|
|
636
|
-
while (currentElement && validSamples < 3) {
|
|
637
|
-
const computedStyle = window.getComputedStyle(currentElement);
|
|
638
|
-
const bgColor = computedStyle.backgroundColor;
|
|
639
|
-
|
|
640
|
-
if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
|
|
641
|
-
const rgb = bgColor.match(/\d+/g);
|
|
642
|
-
if (rgb && rgb.length >= 3) {
|
|
643
|
-
const r = Number(rgb[0]) || 0;
|
|
644
|
-
const g = Number(rgb[1]) || 0;
|
|
645
|
-
const b = Number(rgb[2]) || 0;
|
|
646
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
647
|
-
totalLuminance += luminance;
|
|
648
|
-
validSamples++;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
currentElement = currentElement.parentElement;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Use canvas sampling as fallback for complex backgrounds
|
|
655
|
-
if (validSamples === 0 && typeof document !== 'undefined') {
|
|
656
|
-
try {
|
|
657
|
-
const canvas = document.createElement('canvas');
|
|
658
|
-
const ctx = canvas.getContext('2d');
|
|
659
|
-
if (ctx) {
|
|
660
|
-
canvas.width = 1;
|
|
661
|
-
canvas.height = 1;
|
|
662
|
-
|
|
663
|
-
// Sample the background at element position
|
|
664
|
-
const imageData = ctx.getImageData(0, 0, 1, 1);
|
|
665
|
-
const r = imageData.data[0] || 0;
|
|
666
|
-
const g = imageData.data[1] || 0;
|
|
667
|
-
const b = imageData.data[2] || 0;
|
|
668
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
669
|
-
totalLuminance = luminance;
|
|
670
|
-
validSamples = 1;
|
|
671
|
-
}
|
|
672
|
-
} catch (canvasError) {
|
|
673
|
-
// Canvas sampling failed, use default
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
159
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
console.warn('AtomixGlass: matchMedia not supported, using default preferences');
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
try {
|
|
697
|
-
const mediaQueryReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
698
|
-
const mediaQueryHighContrast = window.matchMedia('(prefers-contrast: high)');
|
|
699
|
-
|
|
700
|
-
setUserPrefersReducedMotion(mediaQueryReducedMotion.matches);
|
|
701
|
-
setUserPrefersHighContrast(mediaQueryHighContrast.matches);
|
|
702
|
-
|
|
703
|
-
const handleReducedMotionChange = (e: MediaQueryListEvent) => {
|
|
704
|
-
setUserPrefersReducedMotion(e.matches);
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
const handleHighContrastChange = (e: MediaQueryListEvent) => {
|
|
708
|
-
setUserPrefersHighContrast(e.matches);
|
|
709
|
-
};
|
|
710
|
-
|
|
711
|
-
if (mediaQueryReducedMotion.addEventListener) {
|
|
712
|
-
mediaQueryReducedMotion.addEventListener('change', handleReducedMotionChange);
|
|
713
|
-
mediaQueryHighContrast.addEventListener('change', handleHighContrastChange);
|
|
714
|
-
} else if (mediaQueryReducedMotion.addListener) {
|
|
715
|
-
mediaQueryReducedMotion.addListener(handleReducedMotionChange);
|
|
716
|
-
mediaQueryHighContrast.addListener(handleHighContrastChange);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return () => {
|
|
720
|
-
try {
|
|
721
|
-
if (mediaQueryReducedMotion.removeEventListener) {
|
|
722
|
-
mediaQueryReducedMotion.removeEventListener('change', handleReducedMotionChange);
|
|
723
|
-
mediaQueryHighContrast.removeEventListener('change', handleHighContrastChange);
|
|
724
|
-
} else if (mediaQueryReducedMotion.removeListener) {
|
|
725
|
-
mediaQueryReducedMotion.removeListener(handleReducedMotionChange);
|
|
726
|
-
mediaQueryHighContrast.removeListener(handleHighContrastChange);
|
|
727
|
-
}
|
|
728
|
-
} catch (cleanupError) {
|
|
729
|
-
console.error('AtomixGlass: Error cleaning up media query listeners:', cleanupError);
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
|
-
} catch (error) {
|
|
733
|
-
console.error('AtomixGlass: Error setting up media queries:', error);
|
|
734
|
-
return undefined;
|
|
735
|
-
}
|
|
736
|
-
}, []);
|
|
737
|
-
|
|
738
|
-
// Derived values are now memoized above
|
|
739
|
-
|
|
740
|
-
const globalMousePosition = useMemo(
|
|
741
|
-
() => externalGlobalMousePosition || internalGlobalMousePosition,
|
|
742
|
-
[externalGlobalMousePosition, internalGlobalMousePosition]
|
|
743
|
-
);
|
|
744
|
-
const mouseOffset = useMemo(
|
|
745
|
-
() => externalMouseOffset || internalMouseOffset,
|
|
746
|
-
[externalMouseOffset, internalMouseOffset]
|
|
160
|
+
// Calculate base style with transforms (only dynamic values)
|
|
161
|
+
// Performance: willChange is set only when transforms are active and effects are enabled
|
|
162
|
+
const baseStyle = useMemo(
|
|
163
|
+
() => ({
|
|
164
|
+
...style,
|
|
165
|
+
...(elasticity !== 0 && !effectiveDisableEffects && {
|
|
166
|
+
transform: transformStyle,
|
|
167
|
+
willChange: 'transform',
|
|
168
|
+
}),
|
|
169
|
+
// Reset willChange when effects are disabled to allow browser optimization
|
|
170
|
+
...(effectiveDisableEffects && {
|
|
171
|
+
willChange: 'auto',
|
|
172
|
+
}),
|
|
173
|
+
}),
|
|
174
|
+
[style, transformStyle, effectiveDisableEffects, elasticity]
|
|
747
175
|
);
|
|
748
176
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
isOverLight,
|
|
763
|
-
threshold: 0.7,
|
|
764
|
-
opacity: 0.4 * hoverIntensity * activeIntensity,
|
|
765
|
-
contrast: 1.3 + mouseInfluence * 0.2,
|
|
766
|
-
brightness: 0.9 + mouseInfluence * 0.1,
|
|
767
|
-
saturationBoost: 1.2 + mouseInfluence * 0.3,
|
|
768
|
-
shadowIntensity: 0.8 + mouseInfluence * 0.4,
|
|
769
|
-
borderOpacity: 0.6 + mouseInfluence * 0.2,
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
if (typeof overLight === 'object' && overLight !== null) {
|
|
773
|
-
const objConfig = overLight as OverLightObjectConfig;
|
|
774
|
-
return {
|
|
775
|
-
...baseConfig,
|
|
776
|
-
threshold: objConfig.threshold || baseConfig.threshold,
|
|
777
|
-
opacity: (objConfig.opacity || 0.4) * hoverIntensity * activeIntensity,
|
|
778
|
-
contrast: (objConfig.contrast || 1.3) + mouseInfluence * 0.2,
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
return baseConfig;
|
|
783
|
-
}, [overLight, getEffectiveOverLight, mouseOffset, isHovered, isActive]);
|
|
784
|
-
|
|
785
|
-
const mouseMoveThrottleRef = useRef<number | null>(null);
|
|
786
|
-
const lastMouseEventRef = useRef<MouseEvent | null>(null);
|
|
787
|
-
|
|
788
|
-
const handleMouseMove = useCallback(
|
|
789
|
-
(e: MouseEvent) => {
|
|
790
|
-
lastMouseEventRef.current = e;
|
|
791
|
-
|
|
792
|
-
if (mouseMoveThrottleRef.current === null) {
|
|
793
|
-
mouseMoveThrottleRef.current = requestAnimationFrame(() => {
|
|
794
|
-
const event = lastMouseEventRef.current;
|
|
795
|
-
if (!event) {
|
|
796
|
-
mouseMoveThrottleRef.current = null;
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
const container = mouseContainer?.current || glassRef.current;
|
|
801
|
-
if (!container) {
|
|
802
|
-
mouseMoveThrottleRef.current = null;
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const startTime = enablePerformanceMonitoring ? performance.now() : 0;
|
|
807
|
-
|
|
808
|
-
const rect = container.getBoundingClientRect();
|
|
809
|
-
if (rect.width === 0 || rect.height === 0) {
|
|
810
|
-
mouseMoveThrottleRef.current = null;
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
const center = calculateElementCenter(rect);
|
|
815
|
-
|
|
816
|
-
setInternalMouseOffset({
|
|
817
|
-
x: ((event.clientX - center.x) / rect.width) * 100,
|
|
818
|
-
y: ((event.clientY - center.y) / rect.height) * 100,
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
setInternalGlobalMousePosition({
|
|
822
|
-
x: event.clientX,
|
|
823
|
-
y: event.clientY,
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
if (enablePerformanceMonitoring) {
|
|
827
|
-
const endTime = performance.now();
|
|
828
|
-
const duration = endTime - startTime;
|
|
829
|
-
if (duration > 5) {
|
|
830
|
-
console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
mouseMoveThrottleRef.current = null;
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
},
|
|
838
|
-
[mouseContainer, enablePerformanceMonitoring]
|
|
177
|
+
// Build className with state modifiers
|
|
178
|
+
const componentClassName = useMemo(
|
|
179
|
+
() =>
|
|
180
|
+
[
|
|
181
|
+
ATOMIX_GLASS.BASE_CLASS,
|
|
182
|
+
effectiveReducedMotion && `${ATOMIX_GLASS.BASE_CLASS}--reduced-motion`,
|
|
183
|
+
effectiveHighContrast && `${ATOMIX_GLASS.BASE_CLASS}--high-contrast`,
|
|
184
|
+
effectiveDisableEffects && `${ATOMIX_GLASS.BASE_CLASS}--disabled-effects`,
|
|
185
|
+
className,
|
|
186
|
+
]
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.join(' '),
|
|
189
|
+
[effectiveReducedMotion, effectiveHighContrast, effectiveDisableEffects, className]
|
|
839
190
|
);
|
|
840
191
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
if (effectiveDisableEffects) {
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
const container = mouseContainer?.current || glassRef.current;
|
|
851
|
-
if (!container) {
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
192
|
+
// Calculate position and size styles
|
|
193
|
+
// Optimize: only depend on specific baseStyle properties used
|
|
194
|
+
const baseStylePosition = baseStyle.position;
|
|
195
|
+
const baseStyleTop = baseStyle.top;
|
|
196
|
+
const baseStyleLeft = baseStyle.left;
|
|
854
197
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
};
|
|
864
|
-
}, [
|
|
865
|
-
handleMouseMove,
|
|
866
|
-
mouseContainer,
|
|
867
|
-
externalGlobalMousePosition,
|
|
868
|
-
externalMouseOffset,
|
|
869
|
-
effectiveDisableEffects,
|
|
870
|
-
]);
|
|
871
|
-
|
|
872
|
-
const calculateDirectionalScale = useCallback(() => {
|
|
873
|
-
if (
|
|
874
|
-
!globalMousePosition.x ||
|
|
875
|
-
!globalMousePosition.y ||
|
|
876
|
-
!glassRef.current ||
|
|
877
|
-
!validateGlassSize(glassSize)
|
|
878
|
-
) {
|
|
879
|
-
return 'scale(1)';
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
const rect = glassRef.current.getBoundingClientRect();
|
|
883
|
-
const center = calculateElementCenter(rect);
|
|
884
|
-
const deltaX = globalMousePosition.x - center.x;
|
|
885
|
-
const deltaY = globalMousePosition.y - center.y;
|
|
886
|
-
|
|
887
|
-
const edgeDistanceX = Math.max(0, Math.abs(deltaX) - glassSize.width / 2);
|
|
888
|
-
const edgeDistanceY = Math.max(0, Math.abs(deltaY) - glassSize.height / 2);
|
|
889
|
-
const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
|
|
890
|
-
|
|
891
|
-
if (edgeDistance > ACTIVATION_ZONE) {
|
|
892
|
-
return 'scale(1)';
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const fadeInFactor = 1 - edgeDistance / ACTIVATION_ZONE;
|
|
896
|
-
const centerDistance = calculateDistance(globalMousePosition, center);
|
|
198
|
+
const positionStyles = useMemo(
|
|
199
|
+
() => ({
|
|
200
|
+
position: (baseStylePosition || 'absolute') as React.CSSProperties['position'],
|
|
201
|
+
top: baseStyleTop || 0,
|
|
202
|
+
left: baseStyleLeft || 0,
|
|
203
|
+
}),
|
|
204
|
+
[baseStylePosition, baseStyleTop, baseStyleLeft]
|
|
205
|
+
);
|
|
897
206
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
207
|
+
// Optimize: only depend on specific baseStyle properties used
|
|
208
|
+
const baseStyleWidth = baseStyle.width;
|
|
209
|
+
const baseStyleHeight = baseStyle.height;
|
|
210
|
+
const glassSizeWidth = glassSize.width;
|
|
211
|
+
const glassSizeHeight = glassSize.height;
|
|
901
212
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
213
|
+
const adjustedSize = useMemo(
|
|
214
|
+
() => ({
|
|
215
|
+
width:
|
|
216
|
+
baseStylePosition !== 'fixed'
|
|
217
|
+
? '100%'
|
|
218
|
+
: baseStyleWidth
|
|
219
|
+
? baseStyleWidth
|
|
220
|
+
: Math.max(glassSizeWidth, 0),
|
|
221
|
+
height:
|
|
222
|
+
baseStylePosition !== 'fixed'
|
|
223
|
+
? '100%'
|
|
224
|
+
: baseStyleHeight
|
|
225
|
+
? baseStyleHeight
|
|
226
|
+
: Math.max(glassSizeHeight, 0),
|
|
227
|
+
}),
|
|
228
|
+
[baseStylePosition, baseStyleWidth, baseStyleHeight, glassSizeWidth, glassSizeHeight]
|
|
229
|
+
);
|
|
905
230
|
|
|
906
|
-
const scaleX =
|
|
907
|
-
1 +
|
|
908
|
-
Math.abs(normalizedX) * stretchIntensity * 0.3 -
|
|
909
|
-
Math.abs(normalizedY) * stretchIntensity * 0.15;
|
|
910
|
-
const scaleY =
|
|
911
|
-
1 +
|
|
912
|
-
Math.abs(normalizedY) * stretchIntensity * 0.3 -
|
|
913
|
-
Math.abs(normalizedX) * stretchIntensity * 0.15;
|
|
914
231
|
|
|
915
|
-
return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`;
|
|
916
|
-
}, [globalMousePosition, elasticity, glassSize]);
|
|
917
232
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
!glassRef.current ||
|
|
923
|
-
!validateGlassSize(glassSize)
|
|
924
|
-
) {
|
|
925
|
-
return 0;
|
|
926
|
-
}
|
|
233
|
+
// Memoize gradient calculations separately for better performance
|
|
234
|
+
// Extract mouse position values for dependency optimization
|
|
235
|
+
const mouseOffsetX = mouseOffset.x;
|
|
236
|
+
const mouseOffsetY = mouseOffset.y;
|
|
927
237
|
|
|
928
|
-
|
|
929
|
-
const
|
|
238
|
+
const gradientCalculations = useMemo(() => {
|
|
239
|
+
const mx = mouseOffsetX;
|
|
240
|
+
const my = mouseOffsetY;
|
|
241
|
+
const { GRADIENT } = ATOMIX_GLASS.CONSTANTS;
|
|
930
242
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
243
|
+
// Calculate gradient angles and stops (optimized)
|
|
244
|
+
const borderGradientAngle =
|
|
245
|
+
GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER;
|
|
246
|
+
const borderStop1 = Math.max(
|
|
247
|
+
GRADIENT.BORDER_STOP_1.MIN,
|
|
248
|
+
GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER
|
|
934
249
|
);
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
250
|
+
const borderStop2 = Math.min(
|
|
251
|
+
GRADIENT.BORDER_STOP_2.MAX,
|
|
252
|
+
GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER
|
|
938
253
|
);
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
254
|
+
const borderOpacity1 =
|
|
255
|
+
GRADIENT.BORDER_OPACITY.BASE_1 +
|
|
256
|
+
Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW;
|
|
257
|
+
const borderOpacity2 =
|
|
258
|
+
GRADIENT.BORDER_OPACITY.BASE_2 +
|
|
259
|
+
Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH;
|
|
260
|
+
const borderOpacity3 =
|
|
261
|
+
GRADIENT.BORDER_OPACITY.BASE_3 +
|
|
262
|
+
Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW;
|
|
263
|
+
const borderOpacity4 =
|
|
264
|
+
GRADIENT.BORDER_OPACITY.BASE_4 +
|
|
265
|
+
Math.abs(mx) * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH;
|
|
266
|
+
|
|
267
|
+
// Hover gradient positions
|
|
268
|
+
const hover1X = GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_1;
|
|
269
|
+
const hover1Y = GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_1;
|
|
270
|
+
const hover2X = GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_2;
|
|
271
|
+
const hover2Y = GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_2;
|
|
272
|
+
const hover3X = GRADIENT.CENTER_POSITION + mx * GRADIENT.HOVER_POSITION.MULTIPLIER_3;
|
|
273
|
+
const hover3Y = GRADIENT.CENTER_POSITION + my * GRADIENT.HOVER_POSITION.MULTIPLIER_3;
|
|
274
|
+
|
|
275
|
+
// Base layer positions
|
|
276
|
+
const baseX = GRADIENT.CENTER_POSITION + mx * GRADIENT.BASE_LAYER_MULTIPLIER;
|
|
277
|
+
const baseY = GRADIENT.CENTER_POSITION + my * GRADIENT.BASE_LAYER_MULTIPLIER;
|
|
952
278
|
|
|
953
279
|
return {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
rafId = requestAnimationFrame(() => {
|
|
974
|
-
if (!isValidElement(glassRef.current)) return;
|
|
975
|
-
|
|
976
|
-
const rect = glassRef.current.getBoundingClientRect();
|
|
977
|
-
if (rect.width <= 0 || rect.height <= 0) return;
|
|
978
|
-
|
|
979
|
-
const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
|
|
980
|
-
const newSize: GlassSize = {
|
|
981
|
-
width: Math.round(rect.width + cornerRadiusOffset),
|
|
982
|
-
height: Math.round(rect.height + cornerRadiusOffset),
|
|
983
|
-
};
|
|
984
|
-
|
|
985
|
-
const cornerRadiusChanged = lastCornerRadius !== cornerRadius;
|
|
986
|
-
const dimensionsChanged =
|
|
987
|
-
newSize.width !== lastSize.width || newSize.height !== lastSize.height;
|
|
988
|
-
|
|
989
|
-
if ((forceUpdate || cornerRadiusChanged || dimensionsChanged) && validateSize(newSize)) {
|
|
990
|
-
lastSize = newSize;
|
|
991
|
-
lastCornerRadius = cornerRadius;
|
|
992
|
-
setGlassSize(newSize);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
rafId = null;
|
|
996
|
-
});
|
|
997
|
-
};
|
|
998
|
-
|
|
999
|
-
let resizeTimeoutId: NodeJS.Timeout | null = null;
|
|
1000
|
-
const debouncedResizeHandler = (): void => {
|
|
1001
|
-
if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
|
|
1002
|
-
resizeTimeoutId = setTimeout(updateGlassSize, 16);
|
|
280
|
+
isOverLight,
|
|
281
|
+
mx,
|
|
282
|
+
my,
|
|
283
|
+
borderGradientAngle,
|
|
284
|
+
borderStop1,
|
|
285
|
+
borderStop2,
|
|
286
|
+
borderOpacity1,
|
|
287
|
+
borderOpacity2,
|
|
288
|
+
borderOpacity3,
|
|
289
|
+
borderOpacity4,
|
|
290
|
+
hover1X,
|
|
291
|
+
hover1Y,
|
|
292
|
+
hover2X,
|
|
293
|
+
hover2Y,
|
|
294
|
+
hover3X,
|
|
295
|
+
hover3Y,
|
|
296
|
+
baseX,
|
|
297
|
+
baseY,
|
|
1003
298
|
};
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
299
|
+
}, [mouseOffsetX, mouseOffsetY, isOverLight]);
|
|
300
|
+
|
|
301
|
+
// Memoize opacity values separately - using design token values where applicable
|
|
302
|
+
// Optimize: extract overLightConfig.opacity to avoid depending on whole object
|
|
303
|
+
const overLightOpacity = overLightConfig.opacity;
|
|
304
|
+
|
|
305
|
+
// Read opacity design tokens from CSS custom properties
|
|
306
|
+
const opacityValues = useMemo(() => {
|
|
307
|
+
// Get opacity values from CSS custom properties with fallbacks
|
|
308
|
+
// These align with design tokens: --atomix-opacity-50, --atomix-opacity-40, etc.
|
|
309
|
+
let opacity50 = 0.5;
|
|
310
|
+
let opacity40 = 0.4;
|
|
311
|
+
let opacity80 = 0.8;
|
|
312
|
+
let opacity0 = 0;
|
|
313
|
+
|
|
314
|
+
// Try to read from CSS custom properties if available (SSR-safe)
|
|
315
|
+
if (typeof window !== 'undefined' && glassRef.current) {
|
|
1013
316
|
try {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
100
|
|
1027
|
-
);
|
|
317
|
+
const computedStyle = window.getComputedStyle(glassRef.current);
|
|
318
|
+
const opacity50Value = computedStyle.getPropertyValue('--atomix-opacity-50').trim();
|
|
319
|
+
const opacity40Value = computedStyle.getPropertyValue('--atomix-opacity-40').trim();
|
|
320
|
+
const opacity80Value = computedStyle.getPropertyValue('--atomix-opacity-80').trim();
|
|
321
|
+
const opacity0Value = computedStyle.getPropertyValue('--atomix-opacity-0').trim();
|
|
322
|
+
|
|
323
|
+
if (opacity50Value) opacity50 = parseFloat(opacity50Value) || 0.5;
|
|
324
|
+
if (opacity40Value) opacity40 = parseFloat(opacity40Value) || 0.4;
|
|
325
|
+
if (opacity80Value) opacity80 = parseFloat(opacity80Value) || 0.8;
|
|
326
|
+
if (opacity0Value) opacity0 = parseFloat(opacity0Value) || 0;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
// Fallback to defaults if reading fails
|
|
1028
329
|
}
|
|
1029
|
-
} else {
|
|
1030
|
-
fallbackInterval = setInterval(
|
|
1031
|
-
() => isValidElement(glassRef.current) && updateGlassSize(),
|
|
1032
|
-
100
|
|
1033
|
-
);
|
|
1034
330
|
}
|
|
1035
331
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
332
|
+
const BASE_OVER_LIGHT_OPACITY = opacity40; // Uses design token
|
|
333
|
+
const OVER_OPACITY_MULTIPLIER = 1.1; // Dynamic multiplier for overlay
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
hover1: isHovered || isActive ? opacity50 : opacity0,
|
|
337
|
+
hover2: isActive ? opacity50 : opacity0,
|
|
338
|
+
hover3: isHovered ? opacity40 : isActive ? opacity80 : opacity0,
|
|
339
|
+
base: isOverLight ? overLightOpacity || BASE_OVER_LIGHT_OPACITY : opacity0,
|
|
340
|
+
over: isOverLight ? (overLightOpacity || BASE_OVER_LIGHT_OPACITY) * OVER_OPACITY_MULTIPLIER : opacity0,
|
|
1044
341
|
};
|
|
1045
|
-
}, [
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
342
|
+
}, [isHovered, isActive, isOverLight, overLightOpacity, glassRef]);
|
|
343
|
+
|
|
344
|
+
// Generate CSS variables for layers (only dynamic values)
|
|
345
|
+
// Optimize: extract specific properties from objects to minimize dependencies
|
|
346
|
+
const gradientIsOverLight = gradientCalculations.isOverLight;
|
|
347
|
+
const gradientMx = gradientCalculations.mx;
|
|
348
|
+
const gradientMy = gradientCalculations.my;
|
|
349
|
+
const gradientBorderGradientAngle = gradientCalculations.borderGradientAngle;
|
|
350
|
+
const gradientBorderStop1 = gradientCalculations.borderStop1;
|
|
351
|
+
const gradientBorderStop2 = gradientCalculations.borderStop2;
|
|
352
|
+
const gradientBorderOpacity1 = gradientCalculations.borderOpacity1;
|
|
353
|
+
const gradientBorderOpacity2 = gradientCalculations.borderOpacity2;
|
|
354
|
+
const gradientBorderOpacity3 = gradientCalculations.borderOpacity3;
|
|
355
|
+
const gradientBorderOpacity4 = gradientCalculations.borderOpacity4;
|
|
356
|
+
const gradientHover1X = gradientCalculations.hover1X;
|
|
357
|
+
const gradientHover1Y = gradientCalculations.hover1Y;
|
|
358
|
+
const gradientHover2X = gradientCalculations.hover2X;
|
|
359
|
+
const gradientHover2Y = gradientCalculations.hover2Y;
|
|
360
|
+
const gradientHover3X = gradientCalculations.hover3X;
|
|
361
|
+
const gradientHover3Y = gradientCalculations.hover3Y;
|
|
362
|
+
const gradientBaseX = gradientCalculations.baseX;
|
|
363
|
+
const gradientBaseY = gradientCalculations.baseY;
|
|
364
|
+
|
|
365
|
+
const positionStylesPosition = positionStyles.position;
|
|
366
|
+
const positionStylesTop = positionStyles.top;
|
|
367
|
+
const positionStylesLeft = positionStyles.left;
|
|
368
|
+
|
|
369
|
+
const adjustedSizeWidth = adjustedSize.width;
|
|
370
|
+
const adjustedSizeHeight = adjustedSize.height;
|
|
371
|
+
|
|
372
|
+
const baseStyleTransform = baseStyle.transform;
|
|
373
|
+
const opacityValuesHover1 = opacityValues.hover1;
|
|
374
|
+
const opacityValuesHover2 = opacityValues.hover2;
|
|
375
|
+
const opacityValuesHover3 = opacityValues.hover3;
|
|
376
|
+
const opacityValuesBase = opacityValues.base;
|
|
377
|
+
const opacityValuesOver = opacityValues.over;
|
|
1072
378
|
|
|
1073
|
-
const directionalScale = useMemo(() => {
|
|
1074
|
-
if (effectiveDisableEffects) {
|
|
1075
|
-
return 'scale(1)';
|
|
1076
|
-
}
|
|
1077
|
-
return calculateDirectionalScale();
|
|
1078
|
-
}, [calculateDirectionalScale, effectiveDisableEffects]);
|
|
1079
|
-
|
|
1080
|
-
const transformStyle = useMemo(() => {
|
|
1081
|
-
if (effectiveDisableEffects) {
|
|
1082
|
-
return isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)';
|
|
1083
|
-
}
|
|
1084
|
-
return `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
|
|
1085
|
-
}, [elasticTranslation, isActive, onClick, directionalScale, effectiveDisableEffects]);
|
|
1086
|
-
|
|
1087
|
-
const baseStyle = useMemo(
|
|
1088
|
-
() => ({
|
|
1089
|
-
...style,
|
|
1090
|
-
...(elasticity !== 0 && {
|
|
1091
|
-
transform: transformStyle,
|
|
1092
|
-
transition: effectiveReducedMotion ? 'none' : 'all ease-out 0.2s',
|
|
1093
|
-
willChange: effectiveDisableEffects ? 'auto' : 'transform',
|
|
1094
|
-
}),
|
|
1095
|
-
...(effectiveHighContrast && {
|
|
1096
|
-
border: '2px solid currentColor',
|
|
1097
|
-
outline: '2px solid transparent',
|
|
1098
|
-
outlineOffset: '2px',
|
|
1099
|
-
}),
|
|
1100
|
-
}),
|
|
1101
|
-
[style, transformStyle, effectiveReducedMotion, effectiveDisableEffects, effectiveHighContrast, elasticity]
|
|
1102
|
-
);
|
|
1103
|
-
|
|
1104
|
-
const positionStyles = useMemo(
|
|
1105
|
-
() => ({
|
|
1106
|
-
position: (baseStyle.position || 'absolute') as React.CSSProperties['position'],
|
|
1107
|
-
top: baseStyle.top || 0,
|
|
1108
|
-
left: baseStyle.left || 0,
|
|
1109
|
-
}),
|
|
1110
|
-
[baseStyle]
|
|
1111
|
-
);
|
|
1112
|
-
|
|
1113
|
-
const adjustedSize = {
|
|
1114
|
-
width:
|
|
1115
|
-
baseStyle.position !== 'fixed'
|
|
1116
|
-
? '100%'
|
|
1117
|
-
: baseStyle.width
|
|
1118
|
-
? baseStyle.width
|
|
1119
|
-
: Math.max(glassSize.width, 0),
|
|
1120
|
-
height:
|
|
1121
|
-
baseStyle.position !== 'fixed'
|
|
1122
|
-
? '100%'
|
|
1123
|
-
: baseStyle.height
|
|
1124
|
-
? baseStyle.height
|
|
1125
|
-
: Math.max(glassSize.height, 0),
|
|
1126
|
-
};
|
|
1127
|
-
|
|
1128
|
-
const glassId = useId();
|
|
1129
379
|
const glassVars = useMemo(() => {
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
380
|
+
// RGB color values for rgba() functions
|
|
381
|
+
// Note: CSS doesn't support rgba(var(--rgb), opacity) syntax, so we use direct values
|
|
382
|
+
// These values align with design tokens: --atomix-white-rgb and --atomix-black-rgb
|
|
383
|
+
// The actual RGB values are defined in SCSS and should match these fallbacks
|
|
384
|
+
// TODO: Consider reading from CSS custom properties if browser support improves
|
|
385
|
+
const whiteColor = '255, 255, 255'; // Matches --atomix-white-rgb design token
|
|
386
|
+
const blackColor = '0, 0, 0'; // Matches --atomix-black-rgb design token
|
|
387
|
+
|
|
1135
388
|
return {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
389
|
+
// Standard CSS custom properties for dynamic values
|
|
390
|
+
'--atomix-glass-radius': `${effectiveCornerRadius}px`,
|
|
391
|
+
'--atomix-glass-transform': baseStyleTransform || 'none',
|
|
392
|
+
'--atomix-glass-transition': effectiveReducedMotion ? 'none' : transitionDuration,
|
|
393
|
+
'--atomix-glass-position': positionStylesPosition,
|
|
394
|
+
'--atomix-glass-top': positionStylesTop !== 'fixed' ? `${positionStylesTop}px` : '0',
|
|
395
|
+
'--atomix-glass-left': positionStylesLeft !== 'fixed' ? `${positionStylesLeft}px` : '0',
|
|
396
|
+
'--atomix-glass-width':
|
|
397
|
+
baseStylePosition !== 'fixed' ? adjustedSizeWidth : `${adjustedSizeWidth}px`,
|
|
398
|
+
'--atomix-glass-height':
|
|
399
|
+
baseStylePosition !== 'fixed' ? adjustedSizeHeight : `${adjustedSizeHeight}px`,
|
|
400
|
+
// Border width: Use spacing token for consistency
|
|
401
|
+
'--atomix-glass-border-width': 'var(--atomix-spacing-0-5, 0.09375rem)',
|
|
402
|
+
'--atomix-glass-blend-mode': gradientIsOverLight ? 'multiply' : 'overlay',
|
|
403
|
+
// Dynamic gradients and backgrounds
|
|
404
|
+
// Note: RGB values use design token-aligned constants (white: 255,255,255; black: 0,0,0)
|
|
405
|
+
'--atomix-glass-border-gradient-1': `linear-gradient(${gradientBorderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${gradientBorderOpacity1}) ${gradientBorderStop1}%, rgba(${whiteColor}, ${gradientBorderOpacity2}) ${gradientBorderStop2}%, rgba(${whiteColor}, 0) 100%)`,
|
|
406
|
+
'--atomix-glass-border-gradient-2': `linear-gradient(${gradientBorderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${gradientBorderOpacity3}) ${gradientBorderStop1}%, rgba(${whiteColor}, ${gradientBorderOpacity4}) ${gradientBorderStop2}%, rgba(${whiteColor}, 0) 100%)`,
|
|
407
|
+
'--atomix-glass-hover-1-opacity': opacityValuesHover1,
|
|
408
|
+
'--atomix-glass-hover-1-gradient': gradientIsOverLight
|
|
409
|
+
? `radial-gradient(circle at ${gradientHover1X}% ${gradientHover1Y}%, 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}%)`
|
|
410
|
+
: `radial-gradient(circle at ${gradientHover1X}% ${gradientHover1Y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_STOP}%)`,
|
|
411
|
+
'--atomix-glass-hover-2-opacity': opacityValuesHover2,
|
|
412
|
+
'--atomix-glass-hover-2-gradient': gradientIsOverLight
|
|
413
|
+
? `radial-gradient(circle at ${gradientHover2X}% ${gradientHover2Y}%, 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}%)`
|
|
414
|
+
: `radial-gradient(circle at ${gradientHover2X}% ${gradientHover2Y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_STOP}%)`,
|
|
415
|
+
'--atomix-glass-hover-3-opacity': opacityValuesHover3,
|
|
416
|
+
'--atomix-glass-hover-3-gradient': gradientIsOverLight
|
|
417
|
+
? `radial-gradient(circle at ${gradientHover3X}% ${gradientHover3Y}%, 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}%)`
|
|
418
|
+
: `radial-gradient(circle at ${gradientHover3X}% ${gradientHover3Y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_STOP}%)`,
|
|
419
|
+
'--atomix-glass-base-opacity': opacityValuesBase,
|
|
420
|
+
'--atomix-glass-base-gradient': gradientIsOverLight
|
|
421
|
+
? `linear-gradient(${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.ANGLE}deg, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_BASE + gradientMx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_BASE + gradientMy * 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 + Math.abs(gradientMx) * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
|
|
422
|
+
: `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.WHITE_OPACITY})`,
|
|
423
|
+
'--atomix-glass-overlay-opacity': opacityValuesOver,
|
|
424
|
+
'--atomix-glass-overlay-gradient': gradientIsOverLight
|
|
425
|
+
? `radial-gradient(circle at ${gradientBaseX}% ${gradientBaseY}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_BASE + Math.abs(gradientMx) * 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 + Math.abs(gradientMy) * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
|
|
426
|
+
: `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.WHITE_OPACITY})`,
|
|
1158
427
|
} as React.CSSProperties;
|
|
1159
|
-
}, [
|
|
1160
|
-
|
|
1161
|
-
|
|
428
|
+
}, [
|
|
429
|
+
// Position styles - only specific properties
|
|
430
|
+
positionStylesPosition,
|
|
431
|
+
positionStylesTop,
|
|
432
|
+
positionStylesLeft,
|
|
433
|
+
// Adjusted size - only specific properties
|
|
434
|
+
adjustedSizeWidth,
|
|
435
|
+
adjustedSizeHeight,
|
|
436
|
+
// Base style - only transform property
|
|
437
|
+
baseStyleTransform,
|
|
438
|
+
baseStylePosition,
|
|
439
|
+
// Other values
|
|
440
|
+
effectiveCornerRadius,
|
|
441
|
+
effectiveReducedMotion,
|
|
442
|
+
transitionDuration,
|
|
443
|
+
// Gradient calculations - extracted properties
|
|
444
|
+
gradientIsOverLight,
|
|
445
|
+
gradientMx,
|
|
446
|
+
gradientMy,
|
|
447
|
+
gradientBorderGradientAngle,
|
|
448
|
+
gradientBorderStop1,
|
|
449
|
+
gradientBorderStop2,
|
|
450
|
+
gradientBorderOpacity1,
|
|
451
|
+
gradientBorderOpacity2,
|
|
452
|
+
gradientBorderOpacity3,
|
|
453
|
+
gradientBorderOpacity4,
|
|
454
|
+
gradientHover1X,
|
|
455
|
+
gradientHover1Y,
|
|
456
|
+
gradientHover2X,
|
|
457
|
+
gradientHover2Y,
|
|
458
|
+
gradientHover3X,
|
|
459
|
+
gradientHover3Y,
|
|
460
|
+
gradientBaseX,
|
|
461
|
+
gradientBaseY,
|
|
462
|
+
// Opacity values - extracted properties
|
|
463
|
+
opacityValuesHover1,
|
|
464
|
+
opacityValuesHover2,
|
|
465
|
+
opacityValuesHover3,
|
|
466
|
+
opacityValuesBase,
|
|
467
|
+
opacityValuesOver,
|
|
468
|
+
]);
|
|
1162
469
|
|
|
1163
470
|
return (
|
|
1164
471
|
<div
|
|
1165
|
-
className=
|
|
1166
|
-
style={
|
|
472
|
+
className={componentClassName}
|
|
473
|
+
style={glassVars}
|
|
1167
474
|
role={role || (onClick ? 'button' : undefined)}
|
|
1168
475
|
tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
|
|
1169
476
|
aria-label={ariaLabel}
|
|
1170
477
|
aria-describedby={ariaDescribedBy}
|
|
1171
|
-
aria-disabled={onClick ? false : undefined}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
? e => {
|
|
1175
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
1176
|
-
e.preventDefault();
|
|
1177
|
-
onClick();
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
: undefined
|
|
1181
|
-
}
|
|
478
|
+
aria-disabled={onClick && effectiveDisableEffects ? true : onClick ? false : undefined}
|
|
479
|
+
aria-pressed={onClick && isActive ? true : onClick ? false : undefined}
|
|
480
|
+
onKeyDown={onClick ? handleKeyDown : undefined}
|
|
1182
481
|
>
|
|
1183
|
-
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
<AtomixGlassContainer
|
|
1184
485
|
ref={glassRef}
|
|
486
|
+
contentRef={contentRef}
|
|
1185
487
|
className={className}
|
|
1186
488
|
style={baseStyle}
|
|
1187
|
-
cornerRadius={
|
|
489
|
+
cornerRadius={effectiveCornerRadius}
|
|
1188
490
|
displacementScale={
|
|
1189
491
|
effectiveDisableEffects
|
|
1190
492
|
? 0
|
|
1191
493
|
: mode === 'shader'
|
|
1192
|
-
? displacementScale *
|
|
494
|
+
? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_DISPLACEMENT
|
|
1193
495
|
: overLightConfig.isOverLight
|
|
1194
|
-
? displacementScale *
|
|
496
|
+
? displacementScale * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.OVER_LIGHT_DISPLACEMENT
|
|
1195
497
|
: displacementScale
|
|
1196
498
|
}
|
|
1197
499
|
blurAmount={effectiveDisableEffects ? 0 : blurAmount}
|
|
1198
500
|
saturation={
|
|
1199
501
|
effectiveHighContrast
|
|
1200
|
-
?
|
|
502
|
+
? ATOMIX_GLASS.CONSTANTS.SATURATION.HIGH_CONTRAST
|
|
1201
503
|
: overLightConfig.isOverLight
|
|
1202
504
|
? saturation * overLightConfig.saturationBoost
|
|
1203
505
|
: saturation
|
|
@@ -1206,21 +508,21 @@ export function AtomixGlass({
|
|
|
1206
508
|
effectiveDisableEffects
|
|
1207
509
|
? 0
|
|
1208
510
|
: mode === 'shader'
|
|
1209
|
-
? aberrationIntensity *
|
|
511
|
+
? aberrationIntensity * ATOMIX_GLASS.CONSTANTS.MULTIPLIERS.SHADER_ABERRATION
|
|
1210
512
|
: aberrationIntensity
|
|
1211
513
|
}
|
|
1212
514
|
glassSize={glassSize}
|
|
1213
515
|
padding={padding}
|
|
1214
516
|
mouseOffset={effectiveDisableEffects ? { x: 0, y: 0 } : mouseOffset}
|
|
1215
517
|
globalMousePosition={effectiveDisableEffects ? { x: 0, y: 0 } : globalMousePosition}
|
|
1216
|
-
onMouseEnter={
|
|
1217
|
-
onMouseLeave={
|
|
1218
|
-
onMouseDown={
|
|
1219
|
-
onMouseUp={
|
|
518
|
+
onMouseEnter={handleMouseEnter}
|
|
519
|
+
onMouseLeave={handleMouseLeave}
|
|
520
|
+
onMouseDown={handleMouseDown}
|
|
521
|
+
onMouseUp={handleMouseUp}
|
|
1220
522
|
active={isActive}
|
|
1221
523
|
isHovered={isHovered}
|
|
1222
524
|
isActive={isActive}
|
|
1223
|
-
overLight={overLightConfig
|
|
525
|
+
overLight={overLightConfig.isOverLight}
|
|
1224
526
|
onClick={onClick}
|
|
1225
527
|
mode={mode}
|
|
1226
528
|
transform={baseStyle.transform}
|
|
@@ -1231,109 +533,77 @@ export function AtomixGlass({
|
|
|
1231
533
|
enableLiquidBlur={enableLiquidBlur}
|
|
1232
534
|
>
|
|
1233
535
|
{children}
|
|
1234
|
-
</
|
|
1235
|
-
{
|
|
536
|
+
</AtomixGlassContainer>
|
|
537
|
+
{Boolean(onClick) && (
|
|
1236
538
|
<>
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
top: `var(--${scopedId}-top)`,
|
|
1242
|
-
left: `var(--${scopedId}-left)`,
|
|
1243
|
-
width: `var(--${scopedId}-w)`,
|
|
1244
|
-
height: `var(--${scopedId}-h)`,
|
|
1245
|
-
borderRadius: `var(--${scopedId}-r)`,
|
|
1246
|
-
transform: `var(--${scopedId}-t)`,
|
|
1247
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1248
|
-
background: `var(--${scopedId}-b1)`,
|
|
1249
|
-
}}
|
|
1250
|
-
/>
|
|
1251
|
-
<span
|
|
1252
|
-
className="atomix-glass__border-2"
|
|
1253
|
-
style={{
|
|
1254
|
-
position: `var(--${scopedId}-pos)` as any,
|
|
1255
|
-
top: `var(--${scopedId}-top)`,
|
|
1256
|
-
left: `var(--${scopedId}-left)`,
|
|
1257
|
-
width: `var(--${scopedId}-w)`,
|
|
1258
|
-
height: `var(--${scopedId}-h)`,
|
|
1259
|
-
borderRadius: `var(--${scopedId}-r)`,
|
|
1260
|
-
transform: `var(--${scopedId}-t)`,
|
|
1261
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1262
|
-
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
1263
|
-
background: `var(--${scopedId}-b2)`,
|
|
1264
|
-
}}
|
|
1265
|
-
/>
|
|
539
|
+
{/* Hover layers - opacity and background set via CSS variables in SCSS */}
|
|
540
|
+
<div className={ATOMIX_GLASS.HOVER_1_CLASS} />
|
|
541
|
+
<div className={ATOMIX_GLASS.HOVER_2_CLASS} />
|
|
542
|
+
<div className={ATOMIX_GLASS.HOVER_3_CLASS} />
|
|
1266
543
|
</>
|
|
1267
544
|
)}
|
|
1268
|
-
|
|
545
|
+
|
|
546
|
+
{/* Background layers for over-light mode */}
|
|
547
|
+
{/* Static styles (pointer-events, will-change) are in SCSS */}
|
|
548
|
+
<div
|
|
549
|
+
className={[
|
|
550
|
+
ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
|
|
551
|
+
ATOMIX_GLASS.BACKGROUND_LAYER_DARK_CLASS,
|
|
552
|
+
isOverLight
|
|
553
|
+
? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
|
|
554
|
+
: ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
|
|
555
|
+
]
|
|
556
|
+
.filter(Boolean)
|
|
557
|
+
.join(' ')}
|
|
558
|
+
style={{
|
|
559
|
+
...positionStyles,
|
|
560
|
+
height: adjustedSize.height,
|
|
561
|
+
width: adjustedSize.width,
|
|
562
|
+
borderRadius: `${effectiveCornerRadius}px`,
|
|
563
|
+
transform: baseStyle.transform,
|
|
564
|
+
}}
|
|
565
|
+
/>
|
|
566
|
+
<div
|
|
567
|
+
className={[
|
|
568
|
+
ATOMIX_GLASS.BACKGROUND_LAYER_CLASS,
|
|
569
|
+
ATOMIX_GLASS.BACKGROUND_LAYER_BLACK_CLASS,
|
|
570
|
+
isOverLight
|
|
571
|
+
? ATOMIX_GLASS.BACKGROUND_LAYER_OVER_LIGHT_CLASS
|
|
572
|
+
: ATOMIX_GLASS.BACKGROUND_LAYER_HIDDEN_CLASS,
|
|
573
|
+
]
|
|
574
|
+
.filter(Boolean)
|
|
575
|
+
.join(' ')}
|
|
576
|
+
style={{
|
|
577
|
+
...positionStyles,
|
|
578
|
+
height: adjustedSize.height,
|
|
579
|
+
width: adjustedSize.width,
|
|
580
|
+
borderRadius: `${effectiveCornerRadius}px`,
|
|
581
|
+
transform: baseStyle.transform,
|
|
582
|
+
}}
|
|
583
|
+
/>
|
|
584
|
+
{shouldRenderOverLightLayers && (
|
|
1269
585
|
<>
|
|
586
|
+
{/* Base and overlay layers - opacity and background set via CSS variables in SCSS */}
|
|
587
|
+
<div className={ATOMIX_GLASS.BASE_LAYER_CLASS} />
|
|
588
|
+
<div className={ATOMIX_GLASS.OVERLAY_LAYER_CLASS} />
|
|
589
|
+
{/* Overlay highlight - opacity and background are dynamic, calculated inline */}
|
|
1270
590
|
<div
|
|
1271
|
-
className=
|
|
1272
|
-
style={{
|
|
1273
|
-
position: 'absolute',
|
|
1274
|
-
inset: 0,
|
|
1275
|
-
borderRadius: `var(--${scopedId}-r)`,
|
|
1276
|
-
transform: `var(--${scopedId}-t)`,
|
|
1277
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1278
|
-
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
1279
|
-
opacity: `var(--${scopedId}-h1-o)`,
|
|
1280
|
-
background: `var(--${scopedId}-h1)`,
|
|
1281
|
-
}}
|
|
1282
|
-
/>
|
|
1283
|
-
<div
|
|
1284
|
-
className="atomix-glass__hover-2"
|
|
1285
|
-
style={{
|
|
1286
|
-
position: 'absolute',
|
|
1287
|
-
inset: 0,
|
|
1288
|
-
borderRadius: `var(--${scopedId}-r)`,
|
|
1289
|
-
transform: `var(--${scopedId}-t)`,
|
|
1290
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1291
|
-
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
1292
|
-
opacity: `var(--${scopedId}-h2-o)`,
|
|
1293
|
-
background: `var(--${scopedId}-h2)`,
|
|
1294
|
-
}}
|
|
1295
|
-
/>
|
|
1296
|
-
<div
|
|
1297
|
-
className="atomix-glass__hover-3"
|
|
591
|
+
className={ATOMIX_GLASS.OVERLAY_HIGHLIGHT_CLASS}
|
|
1298
592
|
style={{
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1304
|
-
mixBlendMode: `var(--${scopedId}-blend)` as any,
|
|
1305
|
-
opacity: `var(--${scopedId}-h3-o)`,
|
|
1306
|
-
background: `var(--${scopedId}-h3)`,
|
|
593
|
+
opacity:
|
|
594
|
+
opacityValues.over *
|
|
595
|
+
ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER,
|
|
596
|
+
background: `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}%)`,
|
|
1307
597
|
}}
|
|
1308
598
|
/>
|
|
1309
599
|
</>
|
|
1310
600
|
)}
|
|
1311
|
-
{
|
|
601
|
+
{enableBorderEffect && (
|
|
1312
602
|
<>
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
inset: 0,
|
|
1318
|
-
borderRadius: `var(--${scopedId}-r)`,
|
|
1319
|
-
transform: `var(--${scopedId}-t)`,
|
|
1320
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1321
|
-
opacity: `var(--${scopedId}-base-o)`,
|
|
1322
|
-
background: `var(--${scopedId}-base)`,
|
|
1323
|
-
}}
|
|
1324
|
-
/>
|
|
1325
|
-
<div
|
|
1326
|
-
className="atomix-glass__overlay"
|
|
1327
|
-
style={{
|
|
1328
|
-
position: 'absolute',
|
|
1329
|
-
inset: 0,
|
|
1330
|
-
borderRadius: `var(--${scopedId}-r)`,
|
|
1331
|
-
transform: `var(--${scopedId}-t)`,
|
|
1332
|
-
transition: `var(--${scopedId}-tr)`,
|
|
1333
|
-
opacity: `var(--${scopedId}-over-o)`,
|
|
1334
|
-
background: `var(--${scopedId}-over)`,
|
|
1335
|
-
}}
|
|
1336
|
-
/>
|
|
603
|
+
{/* Border elements - all styles (static and dynamic via CSS variables) are in SCSS */}
|
|
604
|
+
{/* Position, size, transform, transition, border-radius all use CSS variables set in glassVars */}
|
|
605
|
+
<span className={ATOMIX_GLASS.BORDER_1_CLASS} />
|
|
606
|
+
<span className={ATOMIX_GLASS.BORDER_2_CLASS} />
|
|
1337
607
|
</>
|
|
1338
608
|
)}
|
|
1339
609
|
</div>
|