@shohojdhara/atomix 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +101 -199
  2. package/atomix.config.ts +241 -0
  3. package/dist/atomix.css +269 -189
  4. package/dist/atomix.css.map +1 -0
  5. package/dist/atomix.min.css +15179 -11
  6. package/dist/atomix.min.css.map +1 -0
  7. package/dist/charts.d.ts +1929 -0
  8. package/dist/charts.js +6477 -0
  9. package/dist/charts.js.map +1 -0
  10. package/dist/core.d.ts +1289 -0
  11. package/dist/core.js +3373 -0
  12. package/dist/core.js.map +1 -0
  13. package/dist/forms.d.ts +1085 -0
  14. package/dist/forms.js +2466 -0
  15. package/dist/forms.js.map +1 -0
  16. package/dist/heavy.d.ts +636 -0
  17. package/dist/heavy.js +4566 -0
  18. package/dist/heavy.js.map +1 -0
  19. package/dist/index.d.ts +5171 -4792
  20. package/dist/index.esm.js +6098 -4563
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/index.js +6291 -4747
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.min.js +1 -1
  25. package/dist/index.min.js.map +1 -1
  26. package/dist/layout.d.ts +300 -0
  27. package/dist/layout.js +336 -0
  28. package/dist/layout.js.map +1 -0
  29. package/dist/theme.d.ts +2122 -0
  30. package/dist/theme.js +6084 -0
  31. package/dist/theme.js.map +1 -0
  32. package/package.json +59 -27
  33. package/scripts/atomix-cli.js +544 -16
  34. package/scripts/cli/__tests__/cli-commands.test.js +204 -0
  35. package/scripts/cli/__tests__/utils.test.js +201 -0
  36. package/scripts/cli/__tests__/vitest.config.js +26 -0
  37. package/scripts/cli/interactive-init.js +1 -1
  38. package/scripts/cli/token-manager.js +32 -7
  39. package/scripts/cli/utils.js +347 -0
  40. package/src/components/Accordion/Accordion.stories.tsx +50 -17
  41. package/src/components/Accordion/Accordion.tsx +5 -54
  42. package/src/components/Accordion/index.ts +1 -1
  43. package/src/components/AtomixGlass/AtomixGlass.tsx +65 -31
  44. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +11 -4
  45. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
  46. package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
  47. package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
  48. package/src/components/Avatar/Avatar.stories.tsx +7 -0
  49. package/src/components/Avatar/Avatar.tsx +3 -3
  50. package/src/components/Badge/Badge.stories.tsx +91 -13
  51. package/src/components/Badge/Badge.tsx +3 -3
  52. package/src/components/Block/Block.stories.tsx +7 -23
  53. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
  54. package/src/components/Breadcrumb/Breadcrumb.tsx +3 -3
  55. package/src/components/Button/Button.stories.tsx +141 -22
  56. package/src/components/Button/ButtonGroup.stories.tsx +315 -0
  57. package/src/components/Button/ButtonGroup.tsx +67 -0
  58. package/src/components/Button/index.ts +2 -0
  59. package/src/components/Callout/Callout.stories.tsx +8 -6
  60. package/src/components/Card/Card.stories.tsx +82 -28
  61. package/src/components/Card/ElevationCard.tsx +1 -1
  62. package/src/components/Chart/AnimatedChart.tsx +19 -18
  63. package/src/components/Chart/AreaChart.tsx +5 -2
  64. package/src/components/Chart/BarChart.tsx +1 -1
  65. package/src/components/Chart/BubbleChart.tsx +6 -6
  66. package/src/components/Chart/CandlestickChart.tsx +0 -1
  67. package/src/components/Chart/Chart.stories.tsx +5 -7
  68. package/src/components/Chart/Chart.tsx +0 -16
  69. package/src/components/Chart/ChartRenderer.tsx +1 -1
  70. package/src/components/Chart/ChartToolbar.tsx +1 -0
  71. package/src/components/Chart/DonutChart.tsx +0 -1
  72. package/src/components/Chart/FunnelChart.tsx +1 -2
  73. package/src/components/Chart/GaugeChart.tsx +0 -1
  74. package/src/components/Chart/HeatmapChart.tsx +0 -1
  75. package/src/components/Chart/LineChart.tsx +0 -1
  76. package/src/components/Chart/MultiAxisChart.tsx +0 -1
  77. package/src/components/Chart/PieChart.tsx +0 -1
  78. package/src/components/Chart/RadarChart.tsx +19 -13
  79. package/src/components/Chart/ScatterChart.tsx +3 -4
  80. package/src/components/Chart/TreemapChart.tsx +2 -1
  81. package/src/components/Chart/WaterfallChart.tsx +0 -2
  82. package/src/components/Chart/types.ts +12 -2
  83. package/src/components/Chart/utils.ts +4 -3
  84. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
  85. package/src/components/DataTable/DataTable.stories.tsx +23 -16
  86. package/src/components/DataTable/DataTable.tsx +3 -3
  87. package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
  88. package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
  89. package/src/components/Dropdown/Dropdown.tsx +12 -9
  90. package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
  91. package/src/components/Footer/Footer.stories.tsx +8 -6
  92. package/src/components/Footer/FooterLink.tsx +9 -2
  93. package/src/components/Footer/FooterSection.tsx +3 -3
  94. package/src/components/Form/Checkbox.stories.tsx +7 -0
  95. package/src/components/Form/Checkbox.tsx +3 -3
  96. package/src/components/Form/Form.stories.tsx +7 -0
  97. package/src/components/Form/FormGroup.stories.tsx +9 -1
  98. package/src/components/Form/Input.stories.tsx +69 -16
  99. package/src/components/Form/Input.tsx +4 -2
  100. package/src/components/Form/Radio.stories.tsx +9 -1
  101. package/src/components/Form/Radio.tsx +3 -3
  102. package/src/components/Form/Select.stories.tsx +9 -1
  103. package/src/components/Form/Select.tsx +3 -3
  104. package/src/components/Form/Textarea.stories.tsx +10 -2
  105. package/src/components/Form/Textarea.tsx +4 -2
  106. package/src/components/Hero/Hero.stories.tsx +7 -0
  107. package/src/components/List/List.stories.tsx +10 -3
  108. package/src/components/List/List.tsx +3 -3
  109. package/src/components/List/ListGroup.tsx +3 -1
  110. package/src/components/Messages/Messages.stories.tsx +8 -7
  111. package/src/components/Modal/Modal.stories.tsx +17 -6
  112. package/src/components/Modal/Modal.tsx +3 -3
  113. package/src/components/Navigation/Menu/MegaMenu.tsx +9 -3
  114. package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
  115. package/src/components/Navigation/Menu/Menu.tsx +9 -3
  116. package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
  117. package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
  118. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
  119. package/src/components/Pagination/Pagination.stories.tsx +188 -111
  120. package/src/components/Pagination/Pagination.tsx +88 -7
  121. package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
  122. package/src/components/PhotoViewer/PhotoViewerImage.tsx +2 -2
  123. package/src/components/Popover/Popover.stories.tsx +191 -115
  124. package/src/components/Popover/Popover.tsx +4 -4
  125. package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
  126. package/src/components/Progress/Progress.stories.tsx +79 -49
  127. package/src/components/Progress/Progress.tsx +6 -2
  128. package/src/components/Rating/Rating.stories.tsx +109 -84
  129. package/src/components/Rating/Rating.tsx +5 -2
  130. package/src/components/River/River.stories.tsx +194 -114
  131. package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
  132. package/src/components/Slider/Slider.stories.tsx +7 -0
  133. package/src/components/Slider/Slider.tsx +10 -9
  134. package/src/components/Spinner/Spinner.stories.tsx +15 -11
  135. package/src/components/Spinner/Spinner.tsx +3 -3
  136. package/src/components/Steps/Steps.stories.tsx +132 -98
  137. package/src/components/Tabs/Tabs.stories.tsx +163 -112
  138. package/src/components/Tabs/Tabs.tsx +3 -3
  139. package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
  140. package/src/components/Todo/Todo.stories.tsx +38 -12
  141. package/src/components/Toggle/Toggle.stories.tsx +61 -28
  142. package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
  143. package/src/components/Tooltip/Tooltip.tsx +3 -3
  144. package/src/components/Upload/Upload.stories.tsx +122 -84
  145. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
  146. package/src/components/index.ts +6 -2
  147. package/src/layouts/MasonryGrid/MasonryGrid.tsx +2 -2
  148. package/src/lib/composables/useAtomixGlass.ts +2 -3
  149. package/src/lib/composables/useChartPerformance.ts +102 -78
  150. package/src/lib/composables/useChartScale.ts +10 -0
  151. package/src/lib/composables/useHero.ts +9 -2
  152. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -3
  153. package/src/lib/composables/useNavbar.ts +0 -10
  154. package/src/lib/composables/useSideMenu.ts +1 -0
  155. package/src/lib/composables/useVideoPlayer.ts +3 -2
  156. package/src/lib/config/loader.ts +57 -14
  157. package/src/lib/constants/components.ts +10 -0
  158. package/src/lib/hooks/index.ts +0 -1
  159. package/src/lib/hooks/useComponentCustomization.ts +11 -15
  160. package/src/lib/hooks/usePerformanceMonitor.ts +149 -0
  161. package/src/lib/patterns/index.ts +2 -2
  162. package/src/lib/patterns/slots.tsx +2 -2
  163. package/src/lib/theme/README.md +174 -0
  164. package/src/lib/theme/adapters/index.ts +31 -0
  165. package/src/lib/theme/adapters/themeAdapter.ts +287 -0
  166. package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
  167. package/src/lib/theme/config/configLoader.ts +254 -0
  168. package/src/lib/theme/config/loader.ts +37 -48
  169. package/src/lib/theme/config/types.ts +2 -2
  170. package/src/lib/theme/config/validator.ts +15 -91
  171. package/src/lib/theme/{constants.ts → constants/constants.ts} +0 -18
  172. package/src/lib/theme/constants/index.ts +8 -0
  173. package/src/lib/theme/core/ThemeRegistry.ts +19 -6
  174. package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
  175. package/src/lib/theme/core/composeTheme.ts +155 -0
  176. package/src/lib/theme/core/createTheme.ts +94 -0
  177. package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +10 -6
  178. package/src/lib/theme/core/index.ts +5 -19
  179. package/src/lib/theme/devtools/Comparator.tsx +346 -22
  180. package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
  181. package/src/lib/theme/devtools/Inspector.tsx +335 -51
  182. package/src/lib/theme/devtools/LiveEditor.tsx +489 -112
  183. package/src/lib/theme/devtools/Preview.tsx +471 -221
  184. package/src/lib/theme/{core → devtools}/ThemeValidator.ts +6 -3
  185. package/src/lib/theme/devtools/index.ts +14 -4
  186. package/src/lib/theme/devtools/useHistory.ts +130 -0
  187. package/src/lib/theme/errors/index.ts +12 -0
  188. package/src/lib/theme/generators/cssFile.ts +79 -0
  189. package/src/lib/theme/generators/generateCSS.ts +89 -0
  190. package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +4 -14
  191. package/src/lib/theme/generators/index.ts +19 -0
  192. package/src/lib/theme/i18n/rtl.ts +7 -7
  193. package/src/lib/theme/index.ts +120 -15
  194. package/src/lib/theme/runtime/ThemeApplicator.ts +53 -95
  195. package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
  196. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +4 -4
  197. package/src/lib/theme/runtime/ThemeProvider.tsx +456 -179
  198. package/src/lib/theme/runtime/index.ts +1 -2
  199. package/src/lib/theme/runtime/useTheme.ts +1 -2
  200. package/src/lib/theme/test/testTheme.ts +385 -0
  201. package/src/lib/theme/tokens/index.ts +12 -0
  202. package/src/lib/theme/tokens/tokens.ts +721 -0
  203. package/src/lib/theme/types.ts +6 -42
  204. package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
  205. package/src/lib/theme/utils/index.ts +11 -0
  206. package/src/lib/theme/utils/injectCSS.ts +90 -0
  207. package/src/lib/theme/utils/themeHelpers.ts +78 -0
  208. package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +1 -1
  209. package/src/lib/theme-tools.ts +8 -9
  210. package/src/lib/types/components.ts +93 -34
  211. package/src/lib/types/partProps.ts +0 -16
  212. package/src/lib/utils/componentUtils.ts +1 -1
  213. package/src/lib/utils/fontPreloader.ts +148 -0
  214. package/src/lib/utils/index.ts +11 -0
  215. package/src/lib/utils/memoryMonitor.ts +189 -0
  216. package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
  217. package/src/styles/01-settings/_settings.fonts.scss +2 -5
  218. package/src/styles/02-tools/_tools.button.scss +66 -79
  219. package/src/styles/06-components/_components.atomix-glass.scss +13 -3
  220. package/src/styles/06-components/_components.navbar.scss +0 -6
  221. package/src/styles/06-components/_components.pagination.scss +88 -0
  222. package/scripts/build-themes.js +0 -208
  223. package/scripts/sync-theme-config.js +0 -309
  224. package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -1263
  225. package/src/lib/theme/composeTheme.ts +0 -370
  226. package/src/lib/theme/core/ThemeCache.ts +0 -283
  227. package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
  228. package/src/lib/theme/core/ThemeEngine.ts +0 -657
  229. package/src/lib/theme/createThemeFromConfig.ts +0 -132
  230. package/src/lib/theme/devtools/CLI.ts +0 -364
  231. package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
  232. package/src/lib/theme/runtime/ThemeManager.ts +0 -442
  233. package/src/styles/03-generic/_generated-root.css +0 -5
  234. package/src/themes/README.md +0 -442
  235. package/src/themes/themes.config.js +0 -35
  236. /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
  237. /package/src/lib/theme/{errors.ts → errors/errors.ts} +0 -0
@@ -1,1263 +0,0 @@
1
- import {
2
- type CSSProperties,
3
- forwardRef,
4
- useCallback,
5
- useEffect,
6
- useId,
7
- useRef,
8
- useState,
9
- useMemo,
10
- } from 'react';
11
- import { ShaderDisplacementGenerator, fragmentShaders } from './shader-utils';
12
- import { displacementMap, polarDisplacementMap, prominentDisplacementMap } from './utils';
13
-
14
- const generateShaderDisplacementMap = (width: number, height: number): string => {
15
- try {
16
- const generator = new ShaderDisplacementGenerator({
17
- width,
18
- height,
19
- fragment: fragmentShaders.liquidGlass,
20
- });
21
-
22
- const dataUrl = generator.updateShader();
23
- generator.destroy();
24
-
25
- return dataUrl;
26
- } catch (error) {
27
- return displacementMap;
28
- }
29
- };
30
-
31
- const getMap = (mode: 'standard' | 'polar' | 'prominent' | 'shader', shaderMapUrl?: string) => {
32
- switch (mode) {
33
- case 'standard':
34
- return displacementMap;
35
- case 'polar':
36
- return polarDisplacementMap;
37
- case 'prominent':
38
- return prominentDisplacementMap;
39
- case 'shader':
40
- return shaderMapUrl || displacementMap;
41
- default:
42
- throw new Error(`Invalid mode: ${mode}`);
43
- }
44
- };
45
-
46
- const GlassFilter: React.FC<{
47
- id: string;
48
- displacementScale: number;
49
- aberrationIntensity: number;
50
- width: number;
51
- height: number;
52
- mode: 'standard' | 'polar' | 'prominent' | 'shader';
53
- shaderMapUrl?: string;
54
- }> = ({ id, displacementScale, aberrationIntensity, width, height, mode, shaderMapUrl }) => (
55
- <svg style={{ position: 'absolute', width, height, inset: 0 }} aria-hidden="true">
56
- <defs>
57
- <radialGradient id={`${id}-edge-mask`} cx="50%" cy="50%" r="50%">
58
- <stop offset="0%" stopColor="black" stopOpacity="0" />
59
- <stop
60
- offset={`${Math.max(30, 80 - aberrationIntensity * 2)}%`}
61
- stopColor="black"
62
- stopOpacity="0"
63
- />
64
- <stop offset="100%" stopColor="white" stopOpacity="1" />
65
- </radialGradient>
66
- <filter id={id} x="-35%" y="-35%" width="170%" height="170%" colorInterpolationFilters="sRGB">
67
- <feImage
68
- id="feimage"
69
- x="0"
70
- y="0"
71
- width="100%"
72
- height="100%"
73
- result="DISPLACEMENT_MAP"
74
- href={getMap(mode, shaderMapUrl)}
75
- preserveAspectRatio="xMidYMid slice"
76
- />
77
-
78
- <feColorMatrix
79
- in="DISPLACEMENT_MAP"
80
- type="matrix"
81
- values="0.3 0.3 0.3 0 0
82
- 0.3 0.3 0.3 0 0
83
- 0.3 0.3 0.3 0 0
84
- 0 0 0 1 0"
85
- result="EDGE_INTENSITY"
86
- />
87
- <feComponentTransfer in="EDGE_INTENSITY" result="EDGE_MASK">
88
- <feFuncA type="discrete" tableValues={`0 ${aberrationIntensity * 0.05} 1`} />
89
- </feComponentTransfer>
90
-
91
- <feOffset in="SourceGraphic" dx="0" dy="0" result="CENTER_ORIGINAL" />
92
-
93
- <feDisplacementMap
94
- in="SourceGraphic"
95
- in2="DISPLACEMENT_MAP"
96
- scale={displacementScale * (mode === 'shader' ? 1 : -1)}
97
- xChannelSelector="R"
98
- yChannelSelector="B"
99
- result="RED_DISPLACED"
100
- />
101
- <feColorMatrix
102
- in="RED_DISPLACED"
103
- type="matrix"
104
- values="1 0 0 0 0
105
- 0 0 0 0 0
106
- 0 0 0 0 0
107
- 0 0 0 1 0"
108
- result="RED_CHANNEL"
109
- />
110
-
111
- <feDisplacementMap
112
- in="SourceGraphic"
113
- in2="DISPLACEMENT_MAP"
114
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.05)}
115
- xChannelSelector="R"
116
- yChannelSelector="B"
117
- result="GREEN_DISPLACED"
118
- />
119
- <feColorMatrix
120
- in="GREEN_DISPLACED"
121
- type="matrix"
122
- values="0 0 0 0 0
123
- 0 1 0 0 0
124
- 0 0 0 0 0
125
- 0 0 0 1 0"
126
- result="GREEN_CHANNEL"
127
- />
128
-
129
- <feDisplacementMap
130
- in="SourceGraphic"
131
- in2="DISPLACEMENT_MAP"
132
- scale={displacementScale * ((mode === 'shader' ? 1 : -1) - aberrationIntensity * 0.1)}
133
- xChannelSelector="R"
134
- yChannelSelector="B"
135
- result="BLUE_DISPLACED"
136
- />
137
- <feColorMatrix
138
- in="BLUE_DISPLACED"
139
- type="matrix"
140
- values="0 0 0 0 0
141
- 0 0 0 0 0
142
- 0 0 1 0 0
143
- 0 0 0 1 0"
144
- result="BLUE_CHANNEL"
145
- />
146
-
147
- <feBlend in="GREEN_CHANNEL" in2="BLUE_CHANNEL" mode="screen" result="GB_COMBINED" />
148
- <feBlend in="RED_CHANNEL" in2="GB_COMBINED" mode="screen" result="RGB_COMBINED" />
149
-
150
- <feGaussianBlur
151
- in="RGB_COMBINED"
152
- stdDeviation={Math.max(0.1, 0.5 - aberrationIntensity * 0.1)}
153
- result="ABERRATED_BLURRED"
154
- />
155
-
156
- <feComposite
157
- in="ABERRATED_BLURRED"
158
- in2="EDGE_MASK"
159
- operator="in"
160
- result="EDGE_ABERRATION"
161
- />
162
-
163
- <feComponentTransfer in="EDGE_MASK" result="INVERTED_MASK">
164
- <feFuncA type="table" tableValues="1 0" />
165
- </feComponentTransfer>
166
- <feComposite in="CENTER_ORIGINAL" in2="INVERTED_MASK" operator="in" result="CENTER_CLEAN" />
167
-
168
- <feComposite in="EDGE_ABERRATION" in2="CENTER_CLEAN" operator="over" />
169
- </filter>
170
- </defs>
171
- </svg>
172
- );
173
-
174
- const GlassContainer = forwardRef<
175
- HTMLDivElement,
176
- React.PropsWithChildren<{
177
- className?: string;
178
- style?: React.CSSProperties;
179
- displacementScale?: number;
180
- blurAmount?: number;
181
- saturation?: number;
182
- aberrationIntensity?: number;
183
- mouseOffset?: { x: number; y: number };
184
- globalMousePos?: { x: number; y: number };
185
- onMouseLeave?: () => void;
186
- onMouseEnter?: () => void;
187
- onMouseDown?: () => void;
188
- onMouseUp?: () => void;
189
- active?: boolean;
190
- isHovered?: boolean;
191
- isActive?: boolean;
192
- overLight?: boolean;
193
- cornerRadius?: number;
194
- padding?: string;
195
- glassSize?: { width: number; height: number };
196
-
197
- onClick?: () => void;
198
- mode?: 'standard' | 'polar' | 'prominent' | 'shader';
199
- transform?: string;
200
- }>
201
- >(
202
- (
203
- {
204
- children,
205
- className = '',
206
- style,
207
- displacementScale = 25,
208
- blurAmount = 12,
209
- saturation = 180,
210
- aberrationIntensity = 2,
211
- mouseOffset = { x: 0, y: 0 },
212
- globalMousePos = { x: 0, y: 0 },
213
- onMouseEnter,
214
- onMouseLeave,
215
- onMouseDown,
216
- onMouseUp,
217
- active = false,
218
- isHovered = false,
219
- isActive = false,
220
- overLight = false,
221
- cornerRadius = 0,
222
- padding = '0 0',
223
- glassSize = { width: 0, height: 0 },
224
- onClick,
225
- mode = 'standard',
226
- transform = 'none',
227
- },
228
- ref
229
- ) => {
230
- const filterId = useId();
231
- const [shaderMapUrl, setShaderMapUrl] = useState<string>('');
232
-
233
- const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');
234
-
235
- useEffect(() => {
236
- if (mode === 'shader' && glassSize.width > 0 && glassSize.height > 0) {
237
- try {
238
- const url = generateShaderDisplacementMap(glassSize.width, glassSize.height);
239
- setShaderMapUrl(url);
240
- } catch (error) {
241
- console.warn('Failed to generate shader displacement map:', error);
242
- }
243
- }
244
- }, [mode, glassSize.width, glassSize.height]);
245
-
246
- useEffect(() => {
247
- if (!ref || typeof ref === 'function') return;
248
-
249
- const element = (ref as React.RefObject<HTMLDivElement>).current;
250
- if (!element) return;
251
-
252
- const timeoutId = setTimeout(() => {
253
- try {
254
- element.offsetHeight;
255
- } catch (error) {
256
- console.warn('AtomixGlass: Error in GlassContainer size sync:', error);
257
- }
258
- }, 0);
259
-
260
- return () => clearTimeout(timeoutId);
261
- }, [cornerRadius, glassSize.width, glassSize.height]);
262
-
263
- const liquidBlur = useMemo(() => {
264
- if (!ref || !globalMousePos.x || !globalMousePos.y) {
265
- return {
266
- baseBlur: blurAmount,
267
- edgeBlur: blurAmount * 0.5,
268
- centerBlur: blurAmount * 0.2,
269
- flowBlur: blurAmount * 0.3,
270
- };
271
- }
272
-
273
- const rect = (ref as React.RefObject<HTMLDivElement>).current?.getBoundingClientRect();
274
- if (!rect) {
275
- return {
276
- baseBlur: blurAmount,
277
- edgeBlur: blurAmount * 0.5,
278
- centerBlur: blurAmount * 0.2,
279
- flowBlur: blurAmount * 0.3,
280
- };
281
- }
282
- const centerX = rect.left + rect.width / 2;
283
- const centerY = rect.top + rect.height / 2;
284
-
285
- const deltaX = globalMousePos.x - centerX;
286
- const deltaY = globalMousePos.y - centerY;
287
- const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
288
-
289
- const maxDistance = Math.sqrt(rect.width * rect.width + rect.height * rect.height) / 2;
290
- const normalizedDistance = Math.min(distance / maxDistance, 1);
291
-
292
- const mouseInfluence =
293
- Math.sqrt(mouseOffset.x * mouseOffset.x + mouseOffset.y * mouseOffset.y) / 100;
294
-
295
- const baseBlur = blurAmount + mouseInfluence * blurAmount * 0.4;
296
-
297
- const edgeIntensity = normalizedDistance * 1.5 + mouseInfluence * 0.3;
298
- const edgeBlur = baseBlur * (0.8 + edgeIntensity * 0.6);
299
-
300
- const centerIntensity = (1 - normalizedDistance) * 0.3 + mouseInfluence * 0.2;
301
- const centerBlur = baseBlur * (0.3 + centerIntensity * 0.4);
302
-
303
- const flowDirection = Math.atan2(deltaY, deltaX);
304
- const flowIntensity = Math.sin(flowDirection + mouseInfluence * Math.PI) * 0.5 + 0.5;
305
- const flowBlur = baseBlur * (0.4 + flowIntensity * 0.6);
306
-
307
- const hoverMultiplier = isHovered ? 1.2 : 1;
308
- const activeMultiplier = isActive ? 1.4 : 1;
309
- const stateMultiplier = hoverMultiplier * activeMultiplier;
310
-
311
- return {
312
- baseBlur: Math.max(0.1, baseBlur * stateMultiplier),
313
- edgeBlur: Math.max(0.1, edgeBlur * stateMultiplier),
314
- centerBlur: Math.max(0.1, centerBlur * stateMultiplier),
315
- flowBlur: Math.max(0.1, flowBlur * stateMultiplier),
316
- };
317
- }, [blurAmount, globalMousePos, mouseOffset, isHovered, isActive, ref]);
318
-
319
- const backdropStyle = useMemo(() => {
320
- const dynamicSaturation = saturation + liquidBlur.baseBlur * 20;
321
-
322
- const blurLayers = [
323
- `blur(${liquidBlur.baseBlur}px)`,
324
- // `blur(${liquidBlur.edgeBlur}px)`,
325
- // `blur(${liquidBlur.centerBlur}px)`,
326
- `blur(${liquidBlur.flowBlur}px)`,
327
- ];
328
-
329
- return {
330
- filter: `url(#${filterId})`,
331
- backdropFilter: `${blurLayers.join(' ')} saturate(${Math.min(dynamicSaturation, 200)}%)`,
332
- };
333
- }, [filterId, liquidBlur, saturation]);
334
-
335
- return (
336
- <div
337
- ref={ref}
338
- className={` ${className} ${active ? 'active' : ''}`}
339
- style={style}
340
- onClick={onClick}
341
- >
342
- <div
343
- className="glass"
344
- style={{
345
- position: 'relative',
346
- padding,
347
- borderRadius: `${cornerRadius}px`,
348
- transition: 'all 0.2s ease-out',
349
- boxShadow: overLight
350
- ? '0px 16px 70px rgba(0, 0, 0, 0.75)'
351
- : '0px 12px 40px rgba(0, 0, 0, 0.25)',
352
- }}
353
- onMouseEnter={onMouseEnter}
354
- onMouseLeave={onMouseLeave}
355
- onMouseDown={onMouseDown}
356
- onMouseUp={onMouseUp}
357
- >
358
- <GlassFilter
359
- mode={mode}
360
- id={filterId}
361
- displacementScale={displacementScale}
362
- aberrationIntensity={aberrationIntensity}
363
- width={glassSize.width}
364
- height={glassSize.height}
365
- shaderMapUrl={shaderMapUrl}
366
- />
367
- <span
368
- className="glass__warp"
369
- style={
370
- {
371
- ...backdropStyle,
372
- borderRadius: `${cornerRadius}px`,
373
- position: 'absolute',
374
- inset: '0',
375
- } as CSSProperties
376
- }
377
- />
378
-
379
- <div
380
- style={{
381
- position: 'relative',
382
- zIndex: 1,
383
- textShadow: overLight
384
- ? '0px 2px 12px rgba(0, 0, 0, 0)'
385
- : '0px 2px 12px rgba(0, 0, 0, 0.4)',
386
- }}
387
- >
388
- {children}
389
- </div>
390
- </div>
391
- </div>
392
- );
393
- }
394
- );
395
-
396
- GlassContainer.displayName = 'GlassContainer';
397
-
398
- interface AtomixGlassProps {
399
- children: React.ReactNode;
400
- displacementScale?: number;
401
- blurAmount?: number;
402
- saturation?: number;
403
- aberrationIntensity?: number;
404
- elasticity?: number;
405
- cornerRadius?: number;
406
- globalMousePos?: { x: number; y: number };
407
- mouseOffset?: { x: number; y: number };
408
- mouseContainer?: React.RefObject<HTMLElement | null> | null;
409
- className?: string;
410
- padding?: string;
411
- style?: React.CSSProperties;
412
- overLight?: boolean;
413
- mode?: 'standard' | 'polar' | 'prominent' | 'shader';
414
- onClick?: () => void;
415
-
416
- /**
417
- * Accessibility props
418
- */
419
- 'aria-label'?: string;
420
- 'aria-describedby'?: string;
421
- role?: string;
422
- tabIndex?: number;
423
-
424
- /**
425
- * Performance and accessibility options
426
- */
427
- reducedMotion?: boolean;
428
- highContrast?: boolean;
429
- disableEffects?: boolean;
430
-
431
- /**
432
- * Performance monitoring
433
- */
434
- enablePerformanceMonitoring?: boolean;
435
- }
436
-
437
- export function AtomixGlass({
438
- children,
439
- displacementScale = 70,
440
- blurAmount = 0,
441
- saturation = 140,
442
- aberrationIntensity = 2,
443
- elasticity = 0.15,
444
- cornerRadius = 20,
445
- globalMousePos: externalGlobalMousePos,
446
- mouseOffset: externalMouseOffset,
447
- mouseContainer = null,
448
- className = '',
449
- padding = '0 0',
450
- overLight = false,
451
- style = {},
452
- mode = 'standard',
453
- onClick,
454
-
455
- 'aria-label': ariaLabel,
456
- 'aria-describedby': ariaDescribedBy,
457
- role,
458
- tabIndex,
459
-
460
- reducedMotion = false,
461
- highContrast = false,
462
- disableEffects = false,
463
-
464
- enablePerformanceMonitoring = false,
465
- }: AtomixGlassProps) {
466
- const glassRef = useRef<HTMLDivElement>(null);
467
- const [isHovered, setIsHovered] = useState(false);
468
- const [isActive, setIsActive] = useState(false);
469
- const [glassSize, setGlassSize] = useState({ width: 270, height: 69 });
470
- const [internalGlobalMousePos, setInternalGlobalMousePos] = useState({ x: 0, y: 0 });
471
- const [internalMouseOffset, setInternalMouseOffset] = useState({ x: 0, y: 0 });
472
-
473
- const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
474
- const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
475
-
476
- useEffect(() => {
477
- if (typeof window.matchMedia !== 'function') {
478
- console.warn('AtomixGlass: matchMedia not supported, using default preferences');
479
- return;
480
- }
481
-
482
- try {
483
- const mediaQueryReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
484
- const mediaQueryHighContrast = window.matchMedia('(prefers-contrast: high)');
485
-
486
- setUserPrefersReducedMotion(mediaQueryReducedMotion.matches);
487
- setUserPrefersHighContrast(mediaQueryHighContrast.matches);
488
-
489
- const handleReducedMotionChange = (e: MediaQueryListEvent) => {
490
- setUserPrefersReducedMotion(e.matches);
491
- };
492
-
493
- const handleHighContrastChange = (e: MediaQueryListEvent) => {
494
- setUserPrefersHighContrast(e.matches);
495
- };
496
-
497
- if (mediaQueryReducedMotion.addEventListener) {
498
- mediaQueryReducedMotion.addEventListener('change', handleReducedMotionChange);
499
- mediaQueryHighContrast.addEventListener('change', handleHighContrastChange);
500
- } else if (mediaQueryReducedMotion.addListener) {
501
- mediaQueryReducedMotion.addListener(handleReducedMotionChange);
502
- mediaQueryHighContrast.addListener(handleHighContrastChange);
503
- }
504
-
505
- return () => {
506
- if (mediaQueryReducedMotion.removeEventListener) {
507
- mediaQueryReducedMotion.removeEventListener('change', handleReducedMotionChange);
508
- mediaQueryHighContrast.removeEventListener('change', handleHighContrastChange);
509
- } else if (mediaQueryReducedMotion.removeListener) {
510
- mediaQueryReducedMotion.removeListener(handleReducedMotionChange);
511
- mediaQueryHighContrast.removeListener(handleHighContrastChange);
512
- }
513
- };
514
- } catch (error) {
515
- console.warn('AtomixGlass: Error setting up media queries:', error);
516
- }
517
- }, []);
518
-
519
- const effectiveReducedMotion = reducedMotion || userPrefersReducedMotion;
520
- const effectiveHighContrast = highContrast || userPrefersHighContrast;
521
- const effectiveDisableEffects = disableEffects || effectiveReducedMotion;
522
-
523
- const globalMousePos = externalGlobalMousePos || internalGlobalMousePos;
524
- const mouseOffset = externalMouseOffset || internalMouseOffset;
525
-
526
- const mouseMoveThrottleRef = useRef<number | null>(null);
527
- const lastMouseEventRef = useRef<MouseEvent | null>(null);
528
-
529
- const handleMouseMove = useCallback(
530
- (e: MouseEvent) => {
531
- lastMouseEventRef.current = e;
532
-
533
- if (mouseMoveThrottleRef.current === null) {
534
- mouseMoveThrottleRef.current = requestAnimationFrame(() => {
535
- const event = lastMouseEventRef.current;
536
- if (!event) {
537
- mouseMoveThrottleRef.current = null;
538
- return;
539
- }
540
-
541
- const container = mouseContainer?.current || glassRef.current;
542
- if (!container) {
543
- mouseMoveThrottleRef.current = null;
544
- return;
545
- }
546
-
547
- try {
548
- const startTime = enablePerformanceMonitoring ? performance.now() : 0;
549
-
550
- const rect = container.getBoundingClientRect();
551
- const centerX = rect.left + rect.width / 2;
552
- const centerY = rect.top + rect.height / 2;
553
-
554
- setInternalMouseOffset({
555
- x: ((event.clientX - centerX) / rect.width) * 100,
556
- y: ((event.clientY - centerY) / rect.height) * 100,
557
- });
558
-
559
- setInternalGlobalMousePos({
560
- x: event.clientX,
561
- y: event.clientY,
562
- });
563
-
564
- if (enablePerformanceMonitoring) {
565
- const endTime = performance.now();
566
- const duration = endTime - startTime;
567
- if (duration > 5) {
568
- console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
569
- }
570
- }
571
- } catch (error) {
572
- console.warn('AtomixGlass: Error in mouse tracking:', error);
573
- } finally {
574
- mouseMoveThrottleRef.current = null;
575
- }
576
- });
577
- }
578
- },
579
- [mouseContainer]
580
- );
581
-
582
- useEffect(() => {
583
- if (externalGlobalMousePos && externalMouseOffset) {
584
- return;
585
- }
586
-
587
- if (effectiveDisableEffects) {
588
- return;
589
- }
590
-
591
- const container = mouseContainer?.current || glassRef.current;
592
- if (!container) {
593
- return;
594
- }
595
-
596
- container.addEventListener('mousemove', handleMouseMove, { passive: true });
597
-
598
- return () => {
599
- container.removeEventListener('mousemove', handleMouseMove);
600
- if (mouseMoveThrottleRef.current) {
601
- cancelAnimationFrame(mouseMoveThrottleRef.current);
602
- mouseMoveThrottleRef.current = null;
603
- }
604
- };
605
- }, [
606
- handleMouseMove,
607
- mouseContainer,
608
- externalGlobalMousePos,
609
- externalMouseOffset,
610
- effectiveDisableEffects,
611
- ]);
612
-
613
- const calculateDirectionalScale = useCallback(() => {
614
- if (!globalMousePos.x || !globalMousePos.y || !glassRef.current) {
615
- return 'scale(1)';
616
- }
617
-
618
- const rect = glassRef.current.getBoundingClientRect();
619
- const pillCenterX = rect.left + rect.width / 2;
620
- const pillCenterY = rect.top + rect.height / 2;
621
- const pillWidth = glassSize.width;
622
- const pillHeight = glassSize.height;
623
-
624
- const deltaX = globalMousePos.x - pillCenterX;
625
- const deltaY = globalMousePos.y - pillCenterY;
626
-
627
- const edgeDistanceX = Math.max(0, Math.abs(deltaX) - pillWidth / 2);
628
- const edgeDistanceY = Math.max(0, Math.abs(deltaY) - pillHeight / 2);
629
- const edgeDistance = Math.sqrt(edgeDistanceX * edgeDistanceX + edgeDistanceY * edgeDistanceY);
630
-
631
- const activationZone = 200;
632
-
633
- if (edgeDistance > activationZone) {
634
- return 'scale(1)';
635
- }
636
-
637
- const fadeInFactor = 1 - edgeDistance / activationZone;
638
-
639
- const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
640
- if (centerDistance === 0) {
641
- return 'scale(1)';
642
- }
643
-
644
- const normalizedX = deltaX / centerDistance;
645
- const normalizedY = deltaY / centerDistance;
646
-
647
- const stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * fadeInFactor;
648
-
649
- const scaleX =
650
- 1 +
651
- Math.abs(normalizedX) * stretchIntensity * 0.3 -
652
- Math.abs(normalizedY) * stretchIntensity * 0.15;
653
-
654
- const scaleY =
655
- 1 +
656
- Math.abs(normalizedY) * stretchIntensity * 0.3 -
657
- Math.abs(normalizedX) * stretchIntensity * 0.15;
658
-
659
- return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`;
660
- }, [globalMousePos, elasticity, glassSize]);
661
-
662
- const calculateFadeInFactor = useCallback(() => {
663
- if (!globalMousePos.x || !globalMousePos.y || !glassRef.current) {
664
- return 0;
665
- }
666
-
667
- const rect = glassRef.current.getBoundingClientRect();
668
- const pillCenterX = rect.left + rect.width / 2;
669
- const pillCenterY = rect.top + rect.height / 2;
670
- const pillWidth = glassSize.width;
671
- const pillHeight = glassSize.height;
672
-
673
- const edgeDistanceX = Math.max(0, Math.abs(globalMousePos.x - pillCenterX) - pillWidth / 2);
674
- const edgeDistanceY = Math.max(0, Math.abs(globalMousePos.y - pillCenterY) - pillHeight / 2);
675
- const edgeDistance = Math.sqrt(edgeDistanceX * edgeDistanceX + edgeDistanceY * edgeDistanceY);
676
-
677
- const activationZone = 200;
678
- return edgeDistance > activationZone ? 0 : 1 - edgeDistance / activationZone;
679
- }, [globalMousePos, glassSize]);
680
-
681
- const calculateElasticTranslation = useCallback(() => {
682
- if (!glassRef.current) {
683
- return { x: 0, y: 0 };
684
- }
685
-
686
- const fadeInFactor = calculateFadeInFactor();
687
- const rect = glassRef.current.getBoundingClientRect();
688
- const pillCenterX = rect.left + rect.width / 2;
689
- const pillCenterY = rect.top + rect.height / 2;
690
-
691
- return {
692
- x: (globalMousePos.x - pillCenterX) * elasticity * 0.1 * fadeInFactor,
693
- y: (globalMousePos.y - pillCenterY) * elasticity * 0.1 * fadeInFactor,
694
- };
695
- }, [globalMousePos, elasticity, calculateFadeInFactor]);
696
-
697
- useEffect(() => {
698
- const isValidElement = (element: HTMLElement | null): element is HTMLElement => {
699
- return element !== null && element instanceof HTMLElement && element.isConnected;
700
- };
701
-
702
- let rafId: number | null = null;
703
- let lastSize = { width: 0, height: 0 };
704
- let lastCornerRadius = cornerRadius;
705
-
706
- const updateGlassSize = (forceUpdate = false): void => {
707
- try {
708
- if (rafId !== null) {
709
- cancelAnimationFrame(rafId);
710
- }
711
-
712
- rafId = requestAnimationFrame(() => {
713
- try {
714
- if (!isValidElement(glassRef.current)) {
715
- console.warn('AtomixGlass: Element not available for size calculation');
716
- return;
717
- }
718
-
719
- const rect = glassRef.current.getBoundingClientRect();
720
-
721
- if (rect.width <= 0 || rect.height <= 0) {
722
- console.warn('AtomixGlass: Invalid dimensions detected', {
723
- width: rect.width,
724
- height: rect.height,
725
- });
726
- return;
727
- }
728
-
729
- const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
730
- const newSize = {
731
- width: Math.round(rect.width + cornerRadiusOffset),
732
- height: Math.round(rect.height + cornerRadiusOffset),
733
- };
734
-
735
- const cornerRadiusChanged = lastCornerRadius !== cornerRadius;
736
- const dimensionsChanged =
737
- newSize.width !== lastSize.width || newSize.height !== lastSize.height;
738
-
739
- if (forceUpdate || cornerRadiusChanged || dimensionsChanged) {
740
- lastSize = newSize;
741
- lastCornerRadius = cornerRadius;
742
- setGlassSize(newSize);
743
-
744
- if (enablePerformanceMonitoring && (cornerRadiusChanged || dimensionsChanged)) {
745
- console.log('AtomixGlass: Size updated', {
746
- newSize,
747
- cornerRadius,
748
- cornerRadiusChanged,
749
- dimensionsChanged,
750
- });
751
- }
752
- }
753
- } catch (error) {
754
- console.error('AtomixGlass: Error updating glass size:', error);
755
- } finally {
756
- rafId = null;
757
- }
758
- });
759
- } catch (error) {
760
- console.error('AtomixGlass: Error in updateGlassSize:', error);
761
- }
762
- };
763
-
764
- let resizeTimeoutId: NodeJS.Timeout | null = null;
765
- const debouncedResizeHandler = (): void => {
766
- if (resizeTimeoutId) {
767
- clearTimeout(resizeTimeoutId);
768
- }
769
- resizeTimeoutId = setTimeout(updateGlassSize, 16);
770
- };
771
-
772
- try {
773
- updateGlassSize(true);
774
- } catch (error) {
775
- console.error('AtomixGlass: Error in initial size update:', error);
776
- }
777
-
778
- let resizeObserver: ResizeObserver | null = null;
779
- let fallbackInterval: NodeJS.Timeout | null = null;
780
-
781
- try {
782
- const hasResizeObserver =
783
- typeof ResizeObserver !== 'undefined' &&
784
- typeof ResizeObserver.prototype.observe === 'function';
785
-
786
- if (hasResizeObserver && isValidElement(glassRef.current)) {
787
- try {
788
- resizeObserver = new ResizeObserver(entries => {
789
- try {
790
- for (const entry of entries) {
791
- if (entry.target === glassRef.current) {
792
- updateGlassSize();
793
- break;
794
- }
795
- }
796
- } catch (error) {
797
- console.error('AtomixGlass: Error in ResizeObserver callback:', error);
798
- }
799
- });
800
-
801
- resizeObserver.observe(glassRef.current);
802
- } catch (resizeObserverError) {
803
- console.warn(
804
- 'AtomixGlass: ResizeObserver creation failed, using fallback:',
805
- resizeObserverError
806
- );
807
- fallbackInterval = setInterval(() => {
808
- if (isValidElement(glassRef.current)) {
809
- updateGlassSize();
810
- }
811
- }, 100);
812
- }
813
- } else {
814
- console.warn('AtomixGlass: ResizeObserver not supported, using fallback polling');
815
- fallbackInterval = setInterval(() => {
816
- if (isValidElement(glassRef.current)) {
817
- updateGlassSize();
818
- }
819
- }, 100);
820
- }
821
- } catch (error) {
822
- console.error('AtomixGlass: Error setting up ResizeObserver:', error);
823
- fallbackInterval = setInterval(() => {
824
- if (isValidElement(glassRef.current)) {
825
- updateGlassSize();
826
- }
827
- }, 100);
828
- }
829
-
830
- window.addEventListener('resize', debouncedResizeHandler, { passive: true });
831
-
832
- return () => {
833
- try {
834
- if (rafId !== null) {
835
- cancelAnimationFrame(rafId);
836
- rafId = null;
837
- }
838
-
839
- if (resizeTimeoutId) {
840
- clearTimeout(resizeTimeoutId);
841
- resizeTimeoutId = null;
842
- }
843
-
844
- window.removeEventListener('resize', debouncedResizeHandler);
845
-
846
- if (resizeObserver) {
847
- try {
848
- if (isValidElement(glassRef.current)) {
849
- resizeObserver.unobserve(glassRef.current);
850
- }
851
- resizeObserver.disconnect();
852
- } catch (error) {
853
- console.error('AtomixGlass: Error cleaning up ResizeObserver:', error);
854
- }
855
- resizeObserver = null;
856
- }
857
-
858
- if (fallbackInterval) {
859
- clearInterval(fallbackInterval);
860
- fallbackInterval = null;
861
- }
862
- } catch (error) {
863
- console.error('AtomixGlass: Error in cleanup:', error);
864
- }
865
- };
866
- }, [cornerRadius, enablePerformanceMonitoring]);
867
-
868
- useEffect(() => {
869
- if (!glassRef.current) return;
870
-
871
- const timeoutId = setTimeout(() => {
872
- try {
873
- const rect = glassRef.current?.getBoundingClientRect();
874
- if (rect && rect.width > 0 && rect.height > 0) {
875
- const cornerRadiusOffset = Math.max(0, cornerRadius * 0.1);
876
- const newSize = {
877
- width: Math.round(rect.width + cornerRadiusOffset),
878
- height: Math.round(rect.height + cornerRadiusOffset),
879
- };
880
- setGlassSize(newSize);
881
-
882
- if (enablePerformanceMonitoring) {
883
- console.log('AtomixGlass: Corner radius change triggered size update', {
884
- cornerRadius,
885
- newSize,
886
- });
887
- }
888
- }
889
- } catch (error) {
890
- console.warn('AtomixGlass: Error in corner radius size update:', error);
891
- }
892
- }, 0);
893
-
894
- return () => clearTimeout(timeoutId);
895
- }, [cornerRadius, enablePerformanceMonitoring]);
896
-
897
- const elasticTranslation = useMemo(() => {
898
- if (effectiveDisableEffects) {
899
- return { x: 0, y: 0 };
900
- }
901
- return calculateElasticTranslation();
902
- }, [calculateElasticTranslation, effectiveDisableEffects]);
903
-
904
- const directionalScale = useMemo(() => {
905
- if (effectiveDisableEffects) {
906
- return 'scale(1)';
907
- }
908
- return calculateDirectionalScale();
909
- }, [calculateDirectionalScale, effectiveDisableEffects]);
910
-
911
- const transformStyle = useMemo(() => {
912
- if (effectiveDisableEffects) {
913
- return isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)';
914
- }
915
- return `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
916
- }, [elasticTranslation, isActive, onClick, directionalScale, effectiveDisableEffects]);
917
-
918
- const baseStyle = useMemo(
919
- () => ({
920
- ...style,
921
- transform: transformStyle,
922
- transition: effectiveReducedMotion ? 'none' : 'all ease-out 0.2s',
923
- ...(effectiveHighContrast && {
924
- border: '2px solid currentColor',
925
- outline: '2px solid transparent',
926
- outlineOffset: '2px',
927
- }),
928
- }),
929
- [style, transformStyle, effectiveReducedMotion, effectiveDisableEffects, effectiveHighContrast]
930
- );
931
-
932
- const positionStyles = useMemo(
933
- () => ({
934
- position: (baseStyle.position || 'absolute') as React.CSSProperties['position'],
935
- top: baseStyle.top || 0,
936
- left: baseStyle.left || 0,
937
- }),
938
- [baseStyle]
939
- );
940
-
941
- const getCurrentElementSize = useCallback(() => {
942
- if (!glassRef.current) {
943
- return { width: 0, height: 0 };
944
- }
945
-
946
- try {
947
- const rect = glassRef.current.getBoundingClientRect();
948
- return {
949
- width: Math.max(rect.width, 0),
950
- height: Math.max(rect.height, 0),
951
- };
952
- } catch (error) {
953
- console.warn('AtomixGlass: Error getting current element size:', error);
954
- return { width: 0, height: 0 };
955
- }
956
- }, []);
957
-
958
- const getTransformedSize = useCallback(() => {
959
- const currentSize = getCurrentElementSize();
960
-
961
- if (effectiveDisableEffects || currentSize.width === 0 || currentSize.height === 0) {
962
- return currentSize;
963
- }
964
-
965
- let scaleX = 1;
966
- let scaleY = 1;
967
-
968
- const simpleScaleMatch = directionalScale.match(/scale\(([^)]+)\)/);
969
- if (simpleScaleMatch && simpleScaleMatch[1]) {
970
- const scaleValue = parseFloat(simpleScaleMatch[1]);
971
- scaleX = scaleValue;
972
- scaleY = scaleValue;
973
- } else {
974
- const scaleXMatch = directionalScale.match(/scaleX\(([^)]+)\)/);
975
- if (scaleXMatch && scaleXMatch[1]) {
976
- scaleX = parseFloat(scaleXMatch[1]);
977
- }
978
-
979
- const scaleYMatch = directionalScale.match(/scaleY\(([^)]+)\)/);
980
- if (scaleYMatch && scaleYMatch[1]) {
981
- scaleY = parseFloat(scaleYMatch[1]);
982
- }
983
- }
984
-
985
- const transformedSize = {
986
- width: currentSize.width * scaleX,
987
- height: currentSize.height * scaleY,
988
- };
989
-
990
- if (enablePerformanceMonitoring && (scaleX !== 1 || scaleY !== 1)) {
991
- console.log('AtomixGlass: Scale transformation detected', {
992
- directionalScale,
993
- scaleX,
994
- scaleY,
995
- originalSize: currentSize,
996
- transformedSize,
997
- });
998
- }
999
-
1000
- return transformedSize;
1001
- }, [
1002
- getCurrentElementSize,
1003
- directionalScale,
1004
- effectiveDisableEffects,
1005
- enablePerformanceMonitoring,
1006
- ]);
1007
-
1008
- const borderLayer1Style = useMemo(() => {
1009
- const borderWidth = 1.5;
1010
-
1011
- const adjustedSize = {
1012
- width: baseStyle.position !== 'fixed' ? '100%' : baseStyle.width ? baseStyle.width : Math.max(glassSize.width, 0),
1013
- height: baseStyle.position !== 'fixed' ? '100%' : baseStyle.height ? baseStyle.height : Math.max(glassSize.height, 0),
1014
- };
1015
-
1016
- return {
1017
- ...positionStyles,
1018
-
1019
- width: adjustedSize.width,
1020
- height: adjustedSize.height,
1021
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1022
- transform: baseStyle.transform,
1023
- transition: effectiveReducedMotion ? 'none' : baseStyle.transition,
1024
- overflow: 'hidden',
1025
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1026
- mixBlendMode: 'screen' as React.CSSProperties['mixBlendMode'],
1027
- opacity: 0.2,
1028
- padding: `${borderWidth}px`,
1029
- boxSizing: 'border-box' as React.CSSProperties['boxSizing'],
1030
- zIndex: 5,
1031
- WebkitMask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
1032
- WebkitMaskComposite: 'xor',
1033
- maskComposite: 'exclude',
1034
- boxShadow:
1035
- '0 0 0 0.5px rgba(255, 255, 255, 0.5) inset, 0 1px 3px rgba(255, 255, 255, 0.25) inset, 0 1px 4px rgba(0, 0, 0, 0.35)',
1036
- background: `linear-gradient(
1037
- ${135 + mouseOffset.x * 1.2}deg,
1038
- rgba(255, 255, 255, 0.0) 0%,
1039
- rgba(255, 255, 255, ${0.12 + Math.abs(mouseOffset.x) * 0.008}) ${Math.max(10, 33 + mouseOffset.y * 0.3)}%,
1040
- rgba(255, 255, 255, ${0.4 + Math.abs(mouseOffset.x) * 0.012}) ${Math.min(90, 66 + mouseOffset.y * 0.4)}%,
1041
- rgba(255, 255, 255, 0.0) 100%
1042
- )`,
1043
- };
1044
- }, [
1045
- positionStyles,
1046
- glassSize,
1047
- cornerRadius,
1048
- baseStyle,
1049
- mouseOffset,
1050
- effectiveReducedMotion,
1051
- ]);
1052
-
1053
- const borderLayer2Style = useMemo(() => {
1054
- const borderWidth = 1.5;
1055
-
1056
- const adjustedSize = {
1057
- width: baseStyle.position !== 'fixed' ? '100%' : baseStyle.width ? baseStyle.width : Math.max(glassSize.width, 0),
1058
- height: baseStyle.position !== 'fixed' ? '100%' : baseStyle.height ? baseStyle.height : Math.max(glassSize.height, 0),
1059
- };
1060
-
1061
- return {
1062
- ...positionStyles,
1063
- width: adjustedSize.width,
1064
- height: adjustedSize.height,
1065
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1066
- transform: baseStyle.transform,
1067
- transition: effectiveReducedMotion ? 'none' : baseStyle.transition,
1068
- overflow: 'hidden',
1069
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1070
- zIndex: 6,
1071
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
1072
- padding: `${borderWidth}px`,
1073
- boxSizing: 'border-box' as React.CSSProperties['boxSizing'],
1074
- WebkitMask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
1075
- WebkitMaskComposite: 'xor',
1076
- maskComposite: 'exclude',
1077
- boxShadow:
1078
- '0 0 0 0.5px rgba(255, 255, 255, 0.5) inset, 0 1px 3px rgba(255, 255, 255, 0.25) inset, 0 1px 4px rgba(0, 0, 0, 0.35)',
1079
- background: `linear-gradient(
1080
- ${135 + mouseOffset.x * 1.2}deg,
1081
- rgba(255, 255, 255, 0.0) 0%,
1082
- rgba(255, 255, 255, ${0.32 + Math.abs(mouseOffset.x) * 0.008}) ${Math.max(10, 33 + mouseOffset.y * 0.3)}%,
1083
- rgba(255, 255, 255, ${0.6 + Math.abs(mouseOffset.x) * 0.012}) ${Math.min(90, 66 + mouseOffset.y * 0.4)}%,
1084
- rgba(255, 255, 255, 0.0) 100%
1085
- )`,
1086
- };
1087
- }, [
1088
- positionStyles,
1089
- glassSize,
1090
- cornerRadius,
1091
- baseStyle,
1092
- mouseOffset,
1093
- effectiveReducedMotion,
1094
- ]);
1095
-
1096
- const hoverEffect1Style = useMemo(() => {
1097
- return {
1098
- ...positionStyles,
1099
- position: 'absolute' as React.CSSProperties['position'],
1100
- inset: '0',
1101
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1102
- transform: baseStyle.transform,
1103
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1104
- transition: effectiveReducedMotion ? 'none' : 'all 0.2s ease-out',
1105
- opacity: isHovered || isActive ? 0.5 : 0,
1106
- background: `radial-gradient(
1107
- circle at ${50 + mouseOffset.x / 2}% ${50 + mouseOffset.y / 2}%,
1108
- rgba(255, 255, 255, 0.5) 0%,
1109
- rgba(255, 255, 255, 0) 50%
1110
- )`,
1111
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
1112
- };
1113
- }, [
1114
- positionStyles,
1115
- cornerRadius,
1116
- baseStyle,
1117
- isHovered,
1118
- isActive,
1119
- mouseOffset,
1120
- effectiveReducedMotion,
1121
- ]);
1122
-
1123
- const hoverEffect2Style = useMemo(() => {
1124
- return {
1125
- ...positionStyles,
1126
- position: 'absolute' as React.CSSProperties['position'],
1127
- inset: '0',
1128
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1129
- overflow: 'hidden',
1130
- transform: baseStyle.transform,
1131
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1132
- transition: effectiveReducedMotion ? 'none' : 'all 0.2s ease-out',
1133
- opacity: isActive ? 0.5 : 0,
1134
- background: `radial-gradient(
1135
- circle at ${50 + mouseOffset.x / 1.5}% ${50 + mouseOffset.y / 1.5}%,
1136
- rgba(255, 255, 255, 1) 0%,
1137
- rgba(255, 255, 255, 0) 80%
1138
- )`,
1139
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
1140
- };
1141
- }, [positionStyles, cornerRadius, baseStyle, isActive, mouseOffset, effectiveReducedMotion]);
1142
-
1143
- const hoverEffect3Style = useMemo(() => {
1144
- return {
1145
- ...positionStyles,
1146
- position: 'absolute' as React.CSSProperties['position'],
1147
- inset: '0',
1148
- transform: baseStyle.transform,
1149
- borderRadius: `${Math.max(0, cornerRadius)}px`,
1150
- pointerEvents: 'none' as React.CSSProperties['pointerEvents'],
1151
- transition: effectiveReducedMotion ? 'none' : 'all 0.2s ease-out',
1152
- opacity: isHovered ? 0.4 : isActive ? 0.8 : 0,
1153
- background: `radial-gradient(
1154
- circle at ${50 + mouseOffset.x}% ${50 + mouseOffset.y}%,
1155
- rgba(255, 255, 255, 1) 0%,
1156
- rgba(255, 255, 255, 0) 100%
1157
- )`,
1158
- mixBlendMode: 'overlay' as React.CSSProperties['mixBlendMode'],
1159
- };
1160
- }, [
1161
- positionStyles,
1162
- cornerRadius,
1163
- baseStyle,
1164
- isHovered,
1165
- isActive,
1166
- mouseOffset,
1167
- effectiveReducedMotion,
1168
- ]);
1169
-
1170
- return (
1171
- <div
1172
- style={{ ...positionStyles, position: 'relative' }}
1173
- role={role || (onClick ? 'button' : undefined)}
1174
- tabIndex={onClick ? (tabIndex ?? 0) : tabIndex}
1175
- aria-label={ariaLabel}
1176
- aria-describedby={ariaDescribedBy}
1177
- aria-disabled={onClick ? false : undefined}
1178
- onKeyDown={
1179
- onClick
1180
- ? e => {
1181
- if (e.key === 'Enter' || e.key === ' ') {
1182
- e.preventDefault();
1183
- onClick();
1184
- }
1185
- }
1186
- : undefined
1187
- }
1188
- >
1189
- <div
1190
- className={`u-bg-dark ${overLight ? 'u-opacity-50' : 'u-opacity-0'}`}
1191
- style={{
1192
- ...positionStyles,
1193
- height: glassSize.height,
1194
- width: glassSize.width,
1195
- borderRadius: `${cornerRadius}px`,
1196
- transform: baseStyle.transform,
1197
- transition: baseStyle.transition,
1198
- }}
1199
- />
1200
- <div
1201
- className={`u-bg-black ${overLight ? 'u-opacity-25' : 'u-opacity-0'}`}
1202
- style={{
1203
- ...positionStyles,
1204
- height: glassSize.height,
1205
- width: glassSize.width,
1206
- borderRadius: `${cornerRadius}px`,
1207
- transform: baseStyle.transform,
1208
- transition: baseStyle.transition,
1209
- mixBlendMode: 'overlay',
1210
- pointerEvents: 'none',
1211
- }}
1212
- />
1213
-
1214
- <GlassContainer
1215
- ref={glassRef}
1216
- className={className}
1217
- style={{
1218
- ...baseStyle,
1219
- transform: baseStyle.transform,
1220
- }}
1221
- cornerRadius={cornerRadius}
1222
- displacementScale={
1223
- effectiveDisableEffects ? 0 : overLight ? displacementScale * 0.5 : displacementScale
1224
- }
1225
- blurAmount={effectiveDisableEffects ? 0 : blurAmount}
1226
- saturation={effectiveHighContrast ? 200 : saturation}
1227
- aberrationIntensity={effectiveDisableEffects ? 0 : aberrationIntensity}
1228
- glassSize={glassSize}
1229
- padding={padding}
1230
- mouseOffset={effectiveDisableEffects ? { x: 0, y: 0 } : mouseOffset}
1231
- globalMousePos={effectiveDisableEffects ? { x: 0, y: 0 } : globalMousePos}
1232
- onMouseEnter={() => setIsHovered(true)}
1233
- onMouseLeave={() => setIsHovered(false)}
1234
- onMouseDown={() => setIsActive(true)}
1235
- onMouseUp={() => setIsActive(false)}
1236
- active={isActive}
1237
- isHovered={isHovered}
1238
- isActive={isActive}
1239
- overLight={overLight}
1240
- onClick={onClick}
1241
- mode={effectiveDisableEffects ? 'standard' : mode}
1242
- transform={baseStyle.transform}
1243
- >
1244
- {children}
1245
- </GlassContainer>
1246
-
1247
- <span style={borderLayer1Style} />
1248
-
1249
- <span style={borderLayer2Style} />
1250
-
1251
- {Boolean(onClick) && (
1252
- <>
1253
- <div style={hoverEffect1Style} />
1254
- <div style={hoverEffect2Style} />
1255
- <div style={hoverEffect3Style} />
1256
- </>
1257
- )}
1258
- </div>
1259
- );
1260
- }
1261
-
1262
- export default AtomixGlass;
1263
-