@shohojdhara/atomix 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/atomix.css +1703 -1544
- package/dist/atomix.min.css +4 -4
- package/dist/index.d.ts +1465 -963
- package/dist/index.esm.js +16289 -25908
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +15650 -21780
- 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 +15008 -0
- package/dist/themes/applemix.min.css +72 -0
- package/dist/themes/boomdevs.css +1608 -1450
- package/dist/themes/boomdevs.min.css +5 -5
- package/dist/themes/esrar.css +1702 -1543
- package/dist/themes/esrar.min.css +4 -4
- package/dist/themes/flashtrade.css +15159 -0
- package/dist/themes/flashtrade.min.css +86 -0
- package/dist/themes/mashroom.css +1699 -1540
- package/dist/themes/mashroom.min.css +7 -7
- package/dist/themes/shaj-default.css +1693 -1534
- package/dist/themes/shaj-default.min.css +4 -4
- package/package.json +6 -17
- package/src/components/Accordion/Accordion.stories.tsx +662 -21
- package/src/components/Accordion/Accordion.tsx +21 -14
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +106 -72
- package/src/components/AtomixGlass/AtomixGlass.tsx +529 -1195
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +400 -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 +792 -68
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1250 -0
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +5768 -0
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +1065 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +1129 -0
- package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +395 -0
- package/src/components/AtomixGlass/stories/shared-components.tsx +301 -0
- 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 +76 -55
- package/src/components/Badge/Badge.tsx +12 -14
- package/src/components/Breadcrumb/Breadcrumb.tsx +23 -4
- package/src/components/Button/Button.stories.tsx +501 -20
- package/src/components/Button/Button.tsx +5 -8
- package/src/components/Callout/Callout.stories.tsx +86 -35
- package/src/components/Callout/Callout.tsx +31 -9
- package/src/components/Card/Card.stories.tsx +565 -2
- 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.tsx +6 -3
- package/src/components/Countdown/Countdown.tsx +4 -0
- package/src/components/DataTable/DataTable.tsx +2 -1
- package/src/components/DatePicker/DatePicker.stories.tsx +689 -12
- 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 +473 -2
- package/src/components/EdgePanel/EdgePanel.tsx +101 -13
- 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.stories.tsx +113 -0
- package/src/components/Messages/Messages.tsx +52 -9
- package/src/components/Modal/Modal.stories.tsx +48 -32
- package/src/components/Modal/Modal.tsx +19 -24
- package/src/components/Navigation/Menu/MegaMenu.tsx +2 -2
- package/src/components/Navigation/Menu/Menu.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.stories.tsx +469 -0
- package/src/components/Navigation/Nav/Nav.tsx +22 -4
- package/src/components/Navigation/Nav/NavDropdown.tsx +10 -1
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +413 -0
- package/src/components/Navigation/Navbar/Navbar.tsx +70 -29
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +340 -0
- package/src/components/Navigation/SideMenu/SideMenu.tsx +29 -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 +19 -3
- 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 +19 -3
- 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 +21 -4
- package/src/lib/composables/useChart.ts +227 -370
- package/src/lib/composables/useChartExport.ts +19 -78
- package/src/lib/composables/useChartToolbar.ts +11 -21
- package/src/lib/composables/useEdgePanel.ts +125 -71
- package/src/lib/composables/useFooter.ts +3 -3
- package/src/lib/composables/useGlassContainer.ts +16 -7
- package/src/lib/composables/useLineChart.ts +11 -2
- package/src/lib/composables/usePieChart.ts +4 -14
- 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 +147 -32
- package/src/lib/types/components.ts +355 -25
- 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 +0 -3
- 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.edge-panel.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 +91 -48
- 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 +249 -0
- package/src/styles/06-components/_components.badge.scss +8 -23
- package/src/styles/06-components/_components.button.scss +8 -3
- package/src/styles/06-components/_components.callout.scss +10 -5
- 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.dropdown.scss +19 -7
- package/src/styles/06-components/_components.edge-panel.scss +103 -0
- 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.messages.scss +176 -0
- package/src/styles/06-components/_components.modal.scss +16 -4
- package/src/styles/06-components/_components.navbar.scss +12 -1
- package/src/styles/06-components/_components.side-menu.scss +5 -0
- 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/_index.scss +1 -0
- package/src/styles/99-utilities/_utilities.glass-fixes.scss +1 -0
- package/src/styles/99-utilities/_utilities.scss +1 -1
- package/src/components/AtomixGlass/AtomixGlass.stories.tsx +0 -3011
- package/src/components/AtomixGlass/AtomixGlassComprehensivePreview.stories.tsx +0 -1369
- 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,71 +1,888 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import type {
|
|
9
|
+
AtomixGlassProps,
|
|
10
|
+
DisplacementMode,
|
|
11
|
+
GlassSize,
|
|
12
|
+
MousePosition,
|
|
13
|
+
OverLightConfig,
|
|
14
|
+
OverLightObjectConfig,
|
|
15
|
+
} from '../types/components';
|
|
16
|
+
import { ATOMIX_GLASS } from '../constants/components';
|
|
17
|
+
import {
|
|
18
|
+
calculateDistance,
|
|
19
|
+
calculateElementCenter,
|
|
20
|
+
calculateMouseInfluence,
|
|
21
|
+
extractBorderRadiusFromChildren,
|
|
22
|
+
extractBorderRadiusFromDOMElement,
|
|
23
|
+
validateGlassSize,
|
|
24
|
+
} from '../../components/AtomixGlass/glass-utils';
|
|
25
|
+
|
|
26
|
+
const { CONSTANTS } = ATOMIX_GLASS;
|
|
27
|
+
|
|
28
|
+
interface UseAtomixGlassOptions extends Omit<AtomixGlassProps, 'children'> {
|
|
29
|
+
glassRef: React.RefObject<HTMLDivElement>;
|
|
30
|
+
contentRef: React.RefObject<HTMLDivElement>;
|
|
31
|
+
children?: React.ReactNode;
|
|
10
32
|
}
|
|
11
33
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
34
|
+
interface UseAtomixGlassReturn {
|
|
35
|
+
// State
|
|
36
|
+
isHovered: boolean;
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
glassSize: GlassSize;
|
|
39
|
+
dynamicCornerRadius: number;
|
|
40
|
+
effectiveCornerRadius: number;
|
|
41
|
+
effectiveReducedMotion: boolean;
|
|
42
|
+
effectiveHighContrast: boolean;
|
|
43
|
+
effectiveDisableEffects: boolean;
|
|
44
|
+
detectedOverLight: boolean;
|
|
45
|
+
globalMousePosition: MousePosition;
|
|
46
|
+
mouseOffset: MousePosition;
|
|
47
|
+
|
|
48
|
+
// OverLight config
|
|
49
|
+
overLightConfig: {
|
|
50
|
+
isOverLight: boolean;
|
|
51
|
+
threshold: number;
|
|
52
|
+
opacity: number;
|
|
53
|
+
contrast: number;
|
|
54
|
+
brightness: number;
|
|
55
|
+
saturationBoost: number;
|
|
56
|
+
shadowIntensity: number;
|
|
57
|
+
borderOpacity: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Transform calculations
|
|
61
|
+
elasticTranslation: { x: number; y: number };
|
|
62
|
+
directionalScale: string;
|
|
63
|
+
transformStyle: string;
|
|
64
|
+
|
|
65
|
+
// Event handlers
|
|
66
|
+
handleMouseEnter: () => void;
|
|
67
|
+
handleMouseLeave: () => void;
|
|
68
|
+
handleMouseDown: () => void;
|
|
69
|
+
handleMouseUp: () => void;
|
|
70
|
+
handleMouseMove: (e: MouseEvent) => void;
|
|
71
|
+
handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
15
72
|
}
|
|
16
73
|
|
|
17
74
|
/**
|
|
18
|
-
*
|
|
75
|
+
* Composable hook for AtomixGlass component logic
|
|
76
|
+
* Manages all state, calculations, and event handlers
|
|
19
77
|
*/
|
|
20
|
-
export function useAtomixGlass(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
78
|
+
export function useAtomixGlass({
|
|
79
|
+
glassRef,
|
|
80
|
+
contentRef,
|
|
81
|
+
cornerRadius,
|
|
82
|
+
globalMousePosition: externalGlobalMousePosition,
|
|
83
|
+
mouseOffset: externalMouseOffset,
|
|
84
|
+
mouseContainer,
|
|
85
|
+
overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
|
|
86
|
+
reducedMotion = false,
|
|
87
|
+
highContrast = false,
|
|
88
|
+
disableEffects = false,
|
|
89
|
+
elasticity = 0.05,
|
|
90
|
+
onClick,
|
|
91
|
+
debugCornerRadius = false,
|
|
92
|
+
debugOverLight = false,
|
|
93
|
+
enablePerformanceMonitoring = false,
|
|
94
|
+
children,
|
|
95
|
+
}: UseAtomixGlassOptions): UseAtomixGlassReturn {
|
|
96
|
+
// State
|
|
97
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
98
|
+
const [isActive, setIsActive] = useState(false);
|
|
99
|
+
const [glassSize, setGlassSize] = useState<GlassSize>({ width: 270, height: 69 });
|
|
100
|
+
const [internalGlobalMousePosition, setInternalGlobalMousePosition] = useState<MousePosition>({
|
|
101
|
+
x: 0,
|
|
102
|
+
y: 0,
|
|
103
|
+
});
|
|
104
|
+
const [internalMouseOffset, setInternalMouseOffset] = useState<MousePosition>({ x: 0, y: 0 });
|
|
105
|
+
const [dynamicCornerRadius, setDynamicCornerRadius] = useState<number>(
|
|
106
|
+
CONSTANTS.DEFAULT_CORNER_RADIUS
|
|
107
|
+
);
|
|
108
|
+
const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
|
|
109
|
+
const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
|
|
110
|
+
const [detectedOverLight, setDetectedOverLight] = useState(false);
|
|
24
111
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
112
|
+
// Memoized derived values
|
|
113
|
+
const effectiveCornerRadius = useMemo(() => {
|
|
114
|
+
if (cornerRadius !== undefined) {
|
|
115
|
+
const result = Math.max(0, cornerRadius);
|
|
116
|
+
if (debugCornerRadius) {
|
|
117
|
+
console.log('[AtomixGlass] Using manual cornerRadius prop:', result);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
28
120
|
}
|
|
29
121
|
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
122
|
+
const result = Math.max(0, dynamicCornerRadius);
|
|
123
|
+
if (debugCornerRadius) {
|
|
124
|
+
console.log('[AtomixGlass] Using dynamic cornerRadius:', result);
|
|
33
125
|
}
|
|
126
|
+
return result;
|
|
127
|
+
}, [cornerRadius, dynamicCornerRadius, debugCornerRadius]);
|
|
128
|
+
|
|
129
|
+
const effectiveReducedMotion = useMemo(
|
|
130
|
+
() => reducedMotion || userPrefersReducedMotion,
|
|
131
|
+
[reducedMotion, userPrefersReducedMotion]
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const effectiveHighContrast = useMemo(
|
|
135
|
+
() => highContrast || userPrefersHighContrast,
|
|
136
|
+
[highContrast, userPrefersHighContrast]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const effectiveDisableEffects = useMemo(
|
|
140
|
+
() => disableEffects || effectiveReducedMotion,
|
|
141
|
+
[disableEffects, effectiveReducedMotion]
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const globalMousePosition = useMemo(
|
|
145
|
+
() => externalGlobalMousePosition || internalGlobalMousePosition,
|
|
146
|
+
[externalGlobalMousePosition, internalGlobalMousePosition]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const mouseOffset = useMemo(
|
|
150
|
+
() => externalMouseOffset || internalMouseOffset,
|
|
151
|
+
[externalMouseOffset, internalMouseOffset]
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Extract border-radius from children
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const extractRadius = () => {
|
|
157
|
+
try {
|
|
158
|
+
let extractedRadius: number | null = null;
|
|
159
|
+
let extractionSource = 'default';
|
|
160
|
+
|
|
161
|
+
if (contentRef.current) {
|
|
162
|
+
const firstChild = contentRef.current.firstElementChild as HTMLElement;
|
|
163
|
+
if (firstChild) {
|
|
164
|
+
const domRadius = extractBorderRadiusFromDOMElement(firstChild);
|
|
165
|
+
if (domRadius !== null && domRadius > 0) {
|
|
166
|
+
extractedRadius = domRadius;
|
|
167
|
+
extractionSource = 'DOM element';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (extractedRadius === null) {
|
|
173
|
+
const childRadius = extractBorderRadiusFromChildren(children);
|
|
174
|
+
if (childRadius > 0 && childRadius !== CONSTANTS.DEFAULT_CORNER_RADIUS) {
|
|
175
|
+
extractedRadius = childRadius;
|
|
176
|
+
extractionSource = 'React children';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (extractedRadius !== null && extractedRadius > 0) {
|
|
181
|
+
setDynamicCornerRadius(extractedRadius);
|
|
182
|
+
|
|
183
|
+
if (debugCornerRadius) {
|
|
184
|
+
console.log('[AtomixGlass] Corner radius extracted:', {
|
|
185
|
+
value: extractedRadius,
|
|
186
|
+
source: extractionSource,
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} else if (debugCornerRadius) {
|
|
191
|
+
console.log(
|
|
192
|
+
'[AtomixGlass] No corner radius found, using default:',
|
|
193
|
+
CONSTANTS.DEFAULT_CORNER_RADIUS
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (debugCornerRadius) {
|
|
198
|
+
console.error('[AtomixGlass] Error extracting corner radius:', error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
34
202
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
203
|
+
extractRadius();
|
|
204
|
+
const timeoutId = setTimeout(extractRadius, 100);
|
|
205
|
+
return () => clearTimeout(timeoutId);
|
|
206
|
+
}, [children, debugCornerRadius, contentRef]);
|
|
207
|
+
|
|
208
|
+
// Media query handlers and background detection
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
// Only run auto-detection for 'auto' mode or object config (which uses auto-detection)
|
|
211
|
+
const shouldDetect = (overLight === 'auto' || (typeof overLight === 'object' && overLight !== null));
|
|
212
|
+
|
|
213
|
+
if (shouldDetect && glassRef.current) {
|
|
214
|
+
const timeoutId = setTimeout(() => {
|
|
215
|
+
try {
|
|
216
|
+
const element = glassRef.current;
|
|
217
|
+
if (!element) {
|
|
218
|
+
setDetectedOverLight(false);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Validate window context
|
|
223
|
+
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
|
|
224
|
+
setDetectedOverLight(false);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let totalLuminance = 0;
|
|
229
|
+
let validSamples = 0;
|
|
230
|
+
let hasValidBackground = false;
|
|
231
|
+
|
|
232
|
+
let currentElement = element.parentElement;
|
|
233
|
+
let depth = 0;
|
|
234
|
+
const maxDepth = 20;
|
|
235
|
+
const maxSamples = 10;
|
|
236
|
+
|
|
237
|
+
// Limit traversal depth to prevent infinite loops and performance issues
|
|
238
|
+
while (currentElement && validSamples < maxSamples && depth < maxDepth) {
|
|
239
|
+
try {
|
|
240
|
+
const computedStyle = window.getComputedStyle(currentElement);
|
|
241
|
+
if (!computedStyle) {
|
|
242
|
+
currentElement = currentElement.parentElement;
|
|
243
|
+
depth++;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const bgColor = computedStyle.backgroundColor;
|
|
248
|
+
const bgImage = computedStyle.backgroundImage;
|
|
249
|
+
|
|
250
|
+
// Check for solid color backgrounds
|
|
251
|
+
if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent' && bgColor !== 'initial' && bgColor !== 'none') {
|
|
252
|
+
const rgb = bgColor.match(/\d+/g);
|
|
253
|
+
if (rgb && rgb.length >= 3) {
|
|
254
|
+
const r = Number(rgb[0]);
|
|
255
|
+
const g = Number(rgb[1]);
|
|
256
|
+
const b = Number(rgb[2]);
|
|
257
|
+
|
|
258
|
+
// Validate RGB values are valid numbers
|
|
259
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b) &&
|
|
260
|
+
isFinite(r) && isFinite(g) && isFinite(b) &&
|
|
261
|
+
r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
|
|
262
|
+
// Only consider if it's not pure black or very dark
|
|
263
|
+
if (r > 10 || g > 10 || b > 10) {
|
|
264
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
265
|
+
if (!isNaN(luminance) && isFinite(luminance)) {
|
|
266
|
+
totalLuminance += luminance;
|
|
267
|
+
validSamples++;
|
|
268
|
+
hasValidBackground = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check for image backgrounds
|
|
276
|
+
if (bgImage && bgImage !== 'none' && bgImage !== 'initial') {
|
|
277
|
+
// For image backgrounds, assume medium luminance
|
|
278
|
+
totalLuminance += 0.5;
|
|
279
|
+
validSamples++;
|
|
280
|
+
hasValidBackground = true;
|
|
281
|
+
}
|
|
282
|
+
} catch (styleError) {
|
|
283
|
+
// Silently continue if getting computed style fails for this element
|
|
284
|
+
if (process.env.NODE_ENV === 'development') {
|
|
285
|
+
console.debug('AtomixGlass: Error getting computed style for element:', styleError);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Move to parent element for next iteration
|
|
290
|
+
if (currentElement) {
|
|
291
|
+
currentElement = currentElement.parentElement;
|
|
292
|
+
depth++;
|
|
293
|
+
} else {
|
|
294
|
+
break; // Exit loop if currentElement becomes null
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// More conservative detection with better error handling
|
|
299
|
+
if (hasValidBackground && validSamples > 0) {
|
|
300
|
+
const avgLuminance = totalLuminance / validSamples;
|
|
301
|
+
if (!isNaN(avgLuminance) && isFinite(avgLuminance)) {
|
|
302
|
+
let threshold = 0.7; // Conservative threshold for overlight
|
|
303
|
+
|
|
304
|
+
// If overLight is an object, use its threshold property with validation
|
|
305
|
+
if (typeof overLight === 'object' && overLight !== null) {
|
|
306
|
+
const objConfig = overLight as OverLightObjectConfig;
|
|
307
|
+
if (objConfig.threshold !== undefined) {
|
|
308
|
+
const configThreshold = typeof objConfig.threshold === 'number' &&
|
|
309
|
+
!isNaN(objConfig.threshold) &&
|
|
310
|
+
isFinite(objConfig.threshold)
|
|
311
|
+
? objConfig.threshold
|
|
312
|
+
: 0.7;
|
|
313
|
+
threshold = Math.min(0.9, Math.max(0.1, configThreshold));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const isOverLightDetected = avgLuminance > threshold;
|
|
318
|
+
setDetectedOverLight(isOverLightDetected);
|
|
319
|
+
|
|
320
|
+
// Debug logging
|
|
321
|
+
if (debugOverLight) {
|
|
322
|
+
console.log('[AtomixGlass] OverLight Detection:', {
|
|
323
|
+
avgLuminance: avgLuminance.toFixed(3),
|
|
324
|
+
threshold: threshold.toFixed(3),
|
|
325
|
+
detected: isOverLightDetected,
|
|
326
|
+
validSamples,
|
|
327
|
+
totalLuminance: totalLuminance.toFixed(3),
|
|
328
|
+
configType: typeof overLight === 'object' ? 'object' : typeof overLight,
|
|
329
|
+
timestamp: new Date().toISOString(),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
// Invalid luminance calculation, default to false
|
|
334
|
+
setDetectedOverLight(false);
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
// Default to false if no valid background found
|
|
338
|
+
setDetectedOverLight(false);
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
// Enhanced error logging with context
|
|
342
|
+
if (process.env.NODE_ENV === 'development') {
|
|
343
|
+
console.warn('AtomixGlass: Error detecting background brightness:', error);
|
|
344
|
+
}
|
|
345
|
+
setDetectedOverLight(false);
|
|
346
|
+
}
|
|
347
|
+
}, 150);
|
|
348
|
+
|
|
349
|
+
return () => clearTimeout(timeoutId);
|
|
350
|
+
} else if (typeof overLight === 'boolean') {
|
|
351
|
+
// For boolean values, disable auto-detection
|
|
352
|
+
setDetectedOverLight(false);
|
|
353
|
+
|
|
354
|
+
// Debug logging for boolean mode
|
|
355
|
+
if (debugOverLight) {
|
|
356
|
+
console.log('[AtomixGlass] OverLight Mode: boolean', {
|
|
357
|
+
value: overLight,
|
|
358
|
+
autoDetection: false,
|
|
359
|
+
timestamp: new Date().toISOString(),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
38
362
|
}
|
|
39
363
|
|
|
40
|
-
if (
|
|
41
|
-
|
|
364
|
+
if (typeof window.matchMedia !== 'function') {
|
|
365
|
+
return undefined;
|
|
42
366
|
}
|
|
43
367
|
|
|
44
|
-
|
|
45
|
-
|
|
368
|
+
try {
|
|
369
|
+
const mediaQueryReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
370
|
+
const mediaQueryHighContrast = window.matchMedia('(prefers-contrast: high)');
|
|
371
|
+
|
|
372
|
+
setUserPrefersReducedMotion(mediaQueryReducedMotion.matches);
|
|
373
|
+
setUserPrefersHighContrast(mediaQueryHighContrast.matches);
|
|
374
|
+
|
|
375
|
+
const handleReducedMotionChange = (e: MediaQueryListEvent) => {
|
|
376
|
+
setUserPrefersReducedMotion(e.matches);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const handleHighContrastChange = (e: MediaQueryListEvent) => {
|
|
380
|
+
setUserPrefersHighContrast(e.matches);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
if (mediaQueryReducedMotion.addEventListener) {
|
|
384
|
+
mediaQueryReducedMotion.addEventListener('change', handleReducedMotionChange);
|
|
385
|
+
mediaQueryHighContrast.addEventListener('change', handleHighContrastChange);
|
|
386
|
+
} else if (mediaQueryReducedMotion.addListener) {
|
|
387
|
+
mediaQueryReducedMotion.addListener(handleReducedMotionChange);
|
|
388
|
+
mediaQueryHighContrast.addListener(handleHighContrastChange);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return () => {
|
|
392
|
+
try {
|
|
393
|
+
if (mediaQueryReducedMotion.removeEventListener) {
|
|
394
|
+
mediaQueryReducedMotion.removeEventListener('change', handleReducedMotionChange);
|
|
395
|
+
mediaQueryHighContrast.removeEventListener('change', handleHighContrastChange);
|
|
396
|
+
} else if (mediaQueryReducedMotion.removeListener) {
|
|
397
|
+
mediaQueryReducedMotion.removeListener(handleReducedMotionChange);
|
|
398
|
+
mediaQueryHighContrast.removeListener(handleHighContrastChange);
|
|
399
|
+
}
|
|
400
|
+
} catch (cleanupError) {
|
|
401
|
+
console.error('AtomixGlass: Error cleaning up media query listeners:', cleanupError);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error('AtomixGlass: Error setting up media queries:', error);
|
|
406
|
+
return undefined;
|
|
46
407
|
}
|
|
408
|
+
}, [overLight, glassRef, debugOverLight]);
|
|
409
|
+
|
|
410
|
+
// Mouse tracking
|
|
411
|
+
const mouseMoveThrottleRef = useRef<number | null>(null);
|
|
412
|
+
const lastMouseEventRef = useRef<MouseEvent | null>(null);
|
|
413
|
+
|
|
414
|
+
const handleMouseMove = useCallback(
|
|
415
|
+
(e: MouseEvent) => {
|
|
416
|
+
lastMouseEventRef.current = e;
|
|
417
|
+
|
|
418
|
+
if (mouseMoveThrottleRef.current === null) {
|
|
419
|
+
mouseMoveThrottleRef.current = requestAnimationFrame(() => {
|
|
420
|
+
const event = lastMouseEventRef.current;
|
|
421
|
+
if (!event) {
|
|
422
|
+
mouseMoveThrottleRef.current = null;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
47
425
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
426
|
+
const container = mouseContainer?.current || glassRef.current;
|
|
427
|
+
if (!container) {
|
|
428
|
+
mouseMoveThrottleRef.current = null;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const startTime = enablePerformanceMonitoring ? performance.now() : 0;
|
|
433
|
+
|
|
434
|
+
const rect = container.getBoundingClientRect();
|
|
435
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
436
|
+
mouseMoveThrottleRef.current = null;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const center = calculateElementCenter(rect);
|
|
441
|
+
|
|
442
|
+
setInternalMouseOffset({
|
|
443
|
+
x: ((event.clientX - center.x) / rect.width) * 100,
|
|
444
|
+
y: ((event.clientY - center.y) / rect.height) * 100,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
setInternalGlobalMousePosition({
|
|
448
|
+
x: event.clientX,
|
|
449
|
+
y: event.clientY,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
if (enablePerformanceMonitoring) {
|
|
453
|
+
const endTime = performance.now();
|
|
454
|
+
const duration = endTime - startTime;
|
|
455
|
+
if (duration > 5) {
|
|
456
|
+
console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
mouseMoveThrottleRef.current = null;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
[mouseContainer, glassRef, enablePerformanceMonitoring]
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
useEffect(() => {
|
|
468
|
+
if (externalGlobalMousePosition && externalMouseOffset) {
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (effectiveDisableEffects) {
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const container = mouseContainer?.current || glassRef.current;
|
|
477
|
+
if (!container) {
|
|
478
|
+
return undefined;
|
|
51
479
|
}
|
|
52
480
|
|
|
53
|
-
|
|
54
|
-
|
|
481
|
+
container.addEventListener('mousemove', handleMouseMove, { passive: true });
|
|
482
|
+
|
|
483
|
+
return () => {
|
|
484
|
+
container.removeEventListener('mousemove', handleMouseMove);
|
|
485
|
+
if (mouseMoveThrottleRef.current) {
|
|
486
|
+
cancelAnimationFrame(mouseMoveThrottleRef.current);
|
|
487
|
+
mouseMoveThrottleRef.current = null;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}, [
|
|
491
|
+
handleMouseMove,
|
|
492
|
+
mouseContainer,
|
|
493
|
+
glassRef,
|
|
494
|
+
externalGlobalMousePosition,
|
|
495
|
+
externalMouseOffset,
|
|
496
|
+
effectiveDisableEffects,
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
// Transform calculations
|
|
500
|
+
const calculateDirectionalScale = useCallback(() => {
|
|
501
|
+
if (
|
|
502
|
+
!globalMousePosition.x ||
|
|
503
|
+
!globalMousePosition.y ||
|
|
504
|
+
!glassRef.current ||
|
|
505
|
+
!validateGlassSize(glassSize)
|
|
506
|
+
) {
|
|
507
|
+
return 'scale(1)';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const rect = glassRef.current.getBoundingClientRect();
|
|
511
|
+
const center = calculateElementCenter(rect);
|
|
512
|
+
const deltaX = globalMousePosition.x - center.x;
|
|
513
|
+
const deltaY = globalMousePosition.y - center.y;
|
|
514
|
+
|
|
515
|
+
const edgeDistanceX = Math.max(0, Math.abs(deltaX) - glassSize.width / 2);
|
|
516
|
+
const edgeDistanceY = Math.max(0, Math.abs(deltaY) - glassSize.height / 2);
|
|
517
|
+
const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
|
|
518
|
+
|
|
519
|
+
if (edgeDistance > CONSTANTS.ACTIVATION_ZONE) {
|
|
520
|
+
return 'scale(1)';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const fadeInFactor = 1 - edgeDistance / CONSTANTS.ACTIVATION_ZONE;
|
|
524
|
+
const centerDistance = calculateDistance(globalMousePosition, center);
|
|
525
|
+
|
|
526
|
+
if (centerDistance === 0) {
|
|
527
|
+
return 'scale(1)';
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const normalizedX = deltaX / centerDistance;
|
|
531
|
+
const normalizedY = deltaY / centerDistance;
|
|
532
|
+
const stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * fadeInFactor;
|
|
533
|
+
|
|
534
|
+
const scaleX =
|
|
535
|
+
1 +
|
|
536
|
+
Math.abs(normalizedX) * stretchIntensity * 0.3 -
|
|
537
|
+
Math.abs(normalizedY) * stretchIntensity * 0.15;
|
|
538
|
+
const scaleY =
|
|
539
|
+
1 +
|
|
540
|
+
Math.abs(normalizedY) * stretchIntensity * 0.3 -
|
|
541
|
+
Math.abs(normalizedX) * stretchIntensity * 0.15;
|
|
55
542
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const baseClass = `c-atomix-glass__${element}`;
|
|
543
|
+
return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`;
|
|
544
|
+
}, [globalMousePosition, elasticity, glassSize, glassRef]);
|
|
59
545
|
|
|
60
|
-
|
|
61
|
-
|
|
546
|
+
const calculateFadeInFactor = useCallback(() => {
|
|
547
|
+
if (
|
|
548
|
+
!globalMousePosition.x ||
|
|
549
|
+
!globalMousePosition.y ||
|
|
550
|
+
!glassRef.current ||
|
|
551
|
+
!validateGlassSize(glassSize)
|
|
552
|
+
) {
|
|
553
|
+
return 0;
|
|
62
554
|
}
|
|
63
555
|
|
|
64
|
-
|
|
65
|
-
|
|
556
|
+
const rect = glassRef.current.getBoundingClientRect();
|
|
557
|
+
const center = calculateElementCenter(rect);
|
|
558
|
+
|
|
559
|
+
const edgeDistanceX = Math.max(
|
|
560
|
+
0,
|
|
561
|
+
Math.abs(globalMousePosition.x - center.x) - glassSize.width / 2
|
|
562
|
+
);
|
|
563
|
+
const edgeDistanceY = Math.max(
|
|
564
|
+
0,
|
|
565
|
+
Math.abs(globalMousePosition.y - center.y) - glassSize.height / 2
|
|
566
|
+
);
|
|
567
|
+
const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
|
|
568
|
+
|
|
569
|
+
return edgeDistance > CONSTANTS.ACTIVATION_ZONE ? 0 : 1 - edgeDistance / CONSTANTS.ACTIVATION_ZONE;
|
|
570
|
+
}, [globalMousePosition, glassSize, glassRef]);
|
|
571
|
+
|
|
572
|
+
const calculateElasticTranslation = useCallback(() => {
|
|
573
|
+
if (!glassRef.current) {
|
|
574
|
+
return { x: 0, y: 0 };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const fadeInFactor = calculateFadeInFactor();
|
|
578
|
+
const rect = glassRef.current.getBoundingClientRect();
|
|
579
|
+
const center = calculateElementCenter(rect);
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
x: (globalMousePosition.x - center.x) * elasticity * 0.1 * fadeInFactor,
|
|
583
|
+
y: (globalMousePosition.y - center.y) * elasticity * 0.1 * fadeInFactor,
|
|
584
|
+
};
|
|
585
|
+
}, [globalMousePosition, elasticity, calculateFadeInFactor, glassRef]);
|
|
586
|
+
|
|
587
|
+
const elasticTranslation = useMemo(() => {
|
|
588
|
+
if (effectiveDisableEffects) {
|
|
589
|
+
return { x: 0, y: 0 };
|
|
590
|
+
}
|
|
591
|
+
return calculateElasticTranslation();
|
|
592
|
+
}, [calculateElasticTranslation, effectiveDisableEffects]);
|
|
593
|
+
|
|
594
|
+
const directionalScale = useMemo(() => {
|
|
595
|
+
if (effectiveDisableEffects) {
|
|
596
|
+
return 'scale(1)';
|
|
597
|
+
}
|
|
598
|
+
return calculateDirectionalScale();
|
|
599
|
+
}, [calculateDirectionalScale, effectiveDisableEffects]);
|
|
600
|
+
|
|
601
|
+
const transformStyle = useMemo(() => {
|
|
602
|
+
if (effectiveDisableEffects) {
|
|
603
|
+
return isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)';
|
|
604
|
+
}
|
|
605
|
+
return `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
|
|
606
|
+
}, [elasticTranslation, isActive, onClick, directionalScale, effectiveDisableEffects]);
|
|
607
|
+
|
|
608
|
+
// Size management
|
|
609
|
+
useEffect(() => {
|
|
610
|
+
const isValidElement = (element: HTMLElement | null): element is HTMLElement =>
|
|
611
|
+
element !== null && element instanceof HTMLElement && element.isConnected;
|
|
612
|
+
|
|
613
|
+
const validateSize = (size: GlassSize): boolean =>
|
|
614
|
+
validateGlassSize(size) && size.width <= CONSTANTS.MAX_SIZE && size.height <= CONSTANTS.MAX_SIZE;
|
|
615
|
+
|
|
616
|
+
let rafId: number | null = null;
|
|
617
|
+
let lastSize = { width: 0, height: 0 };
|
|
618
|
+
let lastCornerRadius = effectiveCornerRadius;
|
|
619
|
+
|
|
620
|
+
const updateGlassSize = (forceUpdate = false): void => {
|
|
621
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
622
|
+
|
|
623
|
+
rafId = requestAnimationFrame(() => {
|
|
624
|
+
if (!isValidElement(glassRef.current)) {
|
|
625
|
+
rafId = null;
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const rect = glassRef.current.getBoundingClientRect();
|
|
630
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
631
|
+
rafId = null;
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const cornerRadiusOffset = Math.max(0, Math.min(effectiveCornerRadius * 0.1, 10));
|
|
636
|
+
const newSize: GlassSize = {
|
|
637
|
+
width: Math.round(rect.width + cornerRadiusOffset),
|
|
638
|
+
height: Math.round(rect.height + cornerRadiusOffset),
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const cornerRadiusChanged = lastCornerRadius !== effectiveCornerRadius;
|
|
642
|
+
const dimensionsChanged =
|
|
643
|
+
Math.abs(newSize.width - lastSize.width) > 1 ||
|
|
644
|
+
Math.abs(newSize.height - lastSize.height) > 1;
|
|
645
|
+
|
|
646
|
+
if ((forceUpdate || cornerRadiusChanged || dimensionsChanged) && validateSize(newSize)) {
|
|
647
|
+
lastSize = newSize;
|
|
648
|
+
lastCornerRadius = effectiveCornerRadius;
|
|
649
|
+
setGlassSize(newSize);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
rafId = null;
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
let resizeTimeoutId: NodeJS.Timeout | null = null;
|
|
657
|
+
const debouncedResizeHandler = (): void => {
|
|
658
|
+
if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
|
|
659
|
+
resizeTimeoutId = setTimeout(() => updateGlassSize(false), 16);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const initialTimeoutId = setTimeout(() => updateGlassSize(true), 0);
|
|
663
|
+
|
|
664
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
665
|
+
let fallbackInterval: NodeJS.Timeout | null = null;
|
|
666
|
+
|
|
667
|
+
const hasResizeObserver = typeof ResizeObserver !== 'undefined';
|
|
668
|
+
|
|
669
|
+
if (hasResizeObserver && isValidElement(glassRef.current)) {
|
|
670
|
+
try {
|
|
671
|
+
resizeObserver = new ResizeObserver(entries => {
|
|
672
|
+
for (const entry of entries) {
|
|
673
|
+
if (entry.target === glassRef.current) {
|
|
674
|
+
updateGlassSize(false);
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
resizeObserver.observe(glassRef.current);
|
|
680
|
+
} catch {
|
|
681
|
+
fallbackInterval = setInterval(
|
|
682
|
+
() => isValidElement(glassRef.current) && updateGlassSize(false),
|
|
683
|
+
100
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
fallbackInterval = setInterval(
|
|
688
|
+
() => isValidElement(glassRef.current) && updateGlassSize(false),
|
|
689
|
+
100
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
window.addEventListener('resize', debouncedResizeHandler, { passive: true });
|
|
694
|
+
|
|
695
|
+
return () => {
|
|
696
|
+
clearTimeout(initialTimeoutId);
|
|
697
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
698
|
+
if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
|
|
699
|
+
if (fallbackInterval) clearInterval(fallbackInterval);
|
|
700
|
+
window.removeEventListener('resize', debouncedResizeHandler);
|
|
701
|
+
resizeObserver?.disconnect();
|
|
702
|
+
};
|
|
703
|
+
}, [effectiveCornerRadius, glassRef]);
|
|
704
|
+
|
|
705
|
+
// OverLight config
|
|
706
|
+
/**
|
|
707
|
+
* Get effective overLight value based on configuration
|
|
708
|
+
* - boolean: returns the boolean value directly
|
|
709
|
+
* - 'auto': returns detectedOverLight (auto-detected from background)
|
|
710
|
+
* - object: returns detectedOverLight (auto-detected, but config object provides customization)
|
|
711
|
+
*/
|
|
712
|
+
const getEffectiveOverLight = useCallback(() => {
|
|
713
|
+
if (typeof overLight === 'boolean') {
|
|
714
|
+
return overLight;
|
|
715
|
+
}
|
|
716
|
+
if (overLight === 'auto') {
|
|
717
|
+
return detectedOverLight;
|
|
718
|
+
}
|
|
719
|
+
if (typeof overLight === 'object' && overLight !== null) {
|
|
720
|
+
return detectedOverLight;
|
|
721
|
+
}
|
|
722
|
+
// Default to false for safety when overLight is undefined or invalid
|
|
723
|
+
return false;
|
|
724
|
+
}, [overLight, detectedOverLight]);
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Validate and clamp a numeric config value
|
|
728
|
+
* @param value - The value to validate
|
|
729
|
+
* @param min - Minimum allowed value
|
|
730
|
+
* @param max - Maximum allowed value
|
|
731
|
+
* @param defaultValue - Default value if validation fails
|
|
732
|
+
* @returns Validated and clamped value
|
|
733
|
+
*/
|
|
734
|
+
const validateConfigValue = useCallback(
|
|
735
|
+
(value: unknown, min: number, max: number, defaultValue: number): number => {
|
|
736
|
+
if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
|
|
737
|
+
return defaultValue;
|
|
738
|
+
}
|
|
739
|
+
return Math.min(max, Math.max(min, value));
|
|
740
|
+
},
|
|
741
|
+
[]
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
const overLightConfig = useMemo(() => {
|
|
745
|
+
const isOverLight = getEffectiveOverLight();
|
|
746
|
+
const mouseInfluence = calculateMouseInfluence(mouseOffset);
|
|
747
|
+
const hoverIntensity = isHovered ? 1.4 : 1;
|
|
748
|
+
const activeIntensity = isActive ? 1.6 : 1;
|
|
749
|
+
|
|
750
|
+
// More robust overlight configuration with better defaults and clamping
|
|
751
|
+
const baseOpacity = isOverLight ? Math.min(0.6, Math.max(0.2, 0.5 * hoverIntensity * activeIntensity)) : 0;
|
|
752
|
+
|
|
753
|
+
const baseConfig = {
|
|
754
|
+
isOverLight,
|
|
755
|
+
threshold: 0.7,
|
|
756
|
+
opacity: baseOpacity,
|
|
757
|
+
contrast: Math.min(1.8, Math.max(1.0, 1.4 + mouseInfluence * 0.3)),
|
|
758
|
+
brightness: Math.min(1.2, Math.max(0.7, 0.85 + mouseInfluence * 0.15)),
|
|
759
|
+
saturationBoost: Math.min(2.0, Math.max(1.0, 1.3 + mouseInfluence * 0.4)),
|
|
760
|
+
shadowIntensity: Math.min(1.5, Math.max(0.5, 0.9 + mouseInfluence * 0.5)),
|
|
761
|
+
borderOpacity: Math.min(1.0, Math.max(0.3, 0.7 + mouseInfluence * 0.3)),
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
if (typeof overLight === 'object' && overLight !== null) {
|
|
765
|
+
const objConfig = overLight as OverLightObjectConfig;
|
|
766
|
+
|
|
767
|
+
// Validate and apply object config values with proper clamping
|
|
768
|
+
const validatedThreshold = validateConfigValue(objConfig.threshold, 0.1, 1.0, baseConfig.threshold);
|
|
769
|
+
const validatedOpacity = validateConfigValue(objConfig.opacity, 0.1, 1.0, baseConfig.opacity);
|
|
770
|
+
const validatedContrast = validateConfigValue(objConfig.contrast, 0.5, 2.5, baseConfig.contrast);
|
|
771
|
+
const validatedBrightness = validateConfigValue(objConfig.brightness, 0.5, 2.0, baseConfig.brightness);
|
|
772
|
+
const validatedSaturationBoost = validateConfigValue(objConfig.saturationBoost, 0.5, 3.0, baseConfig.saturationBoost);
|
|
773
|
+
|
|
774
|
+
const finalConfig = {
|
|
775
|
+
...baseConfig,
|
|
776
|
+
threshold: validatedThreshold,
|
|
777
|
+
opacity: validatedOpacity * hoverIntensity * activeIntensity,
|
|
778
|
+
contrast: validatedContrast + mouseInfluence * 0.3,
|
|
779
|
+
brightness: validatedBrightness + mouseInfluence * 0.15,
|
|
780
|
+
saturationBoost: validatedSaturationBoost + mouseInfluence * 0.4,
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// Debug logging
|
|
784
|
+
if (debugOverLight) {
|
|
785
|
+
console.log('[AtomixGlass] OverLight Config:', {
|
|
786
|
+
isOverLight,
|
|
787
|
+
config: {
|
|
788
|
+
threshold: finalConfig.threshold.toFixed(3),
|
|
789
|
+
opacity: finalConfig.opacity.toFixed(3),
|
|
790
|
+
contrast: finalConfig.contrast.toFixed(3),
|
|
791
|
+
brightness: finalConfig.brightness.toFixed(3),
|
|
792
|
+
saturationBoost: finalConfig.saturationBoost.toFixed(3),
|
|
793
|
+
shadowIntensity: finalConfig.shadowIntensity.toFixed(3),
|
|
794
|
+
borderOpacity: finalConfig.borderOpacity.toFixed(3),
|
|
795
|
+
},
|
|
796
|
+
input: {
|
|
797
|
+
threshold: objConfig.threshold,
|
|
798
|
+
opacity: objConfig.opacity,
|
|
799
|
+
contrast: objConfig.contrast,
|
|
800
|
+
brightness: objConfig.brightness,
|
|
801
|
+
saturationBoost: objConfig.saturationBoost,
|
|
802
|
+
},
|
|
803
|
+
dynamic: {
|
|
804
|
+
mouseInfluence: mouseInfluence.toFixed(3),
|
|
805
|
+
hoverIntensity: hoverIntensity.toFixed(3),
|
|
806
|
+
activeIntensity: activeIntensity.toFixed(3),
|
|
807
|
+
},
|
|
808
|
+
timestamp: new Date().toISOString(),
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return finalConfig;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Debug logging for non-object configs
|
|
816
|
+
if (debugOverLight) {
|
|
817
|
+
console.log('[AtomixGlass] OverLight Config:', {
|
|
818
|
+
isOverLight,
|
|
819
|
+
configType: typeof overLight === 'boolean' ? (overLight ? 'true' : 'false') : overLight,
|
|
820
|
+
config: {
|
|
821
|
+
threshold: baseConfig.threshold.toFixed(3),
|
|
822
|
+
opacity: baseConfig.opacity.toFixed(3),
|
|
823
|
+
contrast: baseConfig.contrast.toFixed(3),
|
|
824
|
+
brightness: baseConfig.brightness.toFixed(3),
|
|
825
|
+
saturationBoost: baseConfig.saturationBoost.toFixed(3),
|
|
826
|
+
shadowIntensity: baseConfig.shadowIntensity.toFixed(3),
|
|
827
|
+
borderOpacity: baseConfig.borderOpacity.toFixed(3),
|
|
828
|
+
},
|
|
829
|
+
dynamic: {
|
|
830
|
+
mouseInfluence: mouseInfluence.toFixed(3),
|
|
831
|
+
hoverIntensity: hoverIntensity.toFixed(3),
|
|
832
|
+
activeIntensity: activeIntensity.toFixed(3),
|
|
833
|
+
},
|
|
834
|
+
timestamp: new Date().toISOString(),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return baseConfig;
|
|
839
|
+
}, [overLight, getEffectiveOverLight, mouseOffset, isHovered, isActive, validateConfigValue, debugOverLight]);
|
|
840
|
+
|
|
841
|
+
// Event handlers
|
|
842
|
+
const handleMouseEnter = useCallback(() => setIsHovered(true), []);
|
|
843
|
+
const handleMouseLeave = useCallback(() => setIsHovered(false), []);
|
|
844
|
+
const handleMouseDown = useCallback(() => setIsActive(true), []);
|
|
845
|
+
const handleMouseUp = useCallback(() => setIsActive(false), []);
|
|
846
|
+
|
|
847
|
+
const handleKeyDown = useCallback(
|
|
848
|
+
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
849
|
+
if (onClick && (e.key === 'Enter' || e.key === ' ')) {
|
|
850
|
+
e.preventDefault();
|
|
851
|
+
onClick();
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
[onClick]
|
|
855
|
+
);
|
|
66
856
|
|
|
67
857
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
858
|
+
// State
|
|
859
|
+
isHovered,
|
|
860
|
+
isActive,
|
|
861
|
+
glassSize,
|
|
862
|
+
dynamicCornerRadius,
|
|
863
|
+
effectiveCornerRadius,
|
|
864
|
+
effectiveReducedMotion,
|
|
865
|
+
effectiveHighContrast,
|
|
866
|
+
effectiveDisableEffects,
|
|
867
|
+
detectedOverLight,
|
|
868
|
+
globalMousePosition,
|
|
869
|
+
mouseOffset,
|
|
870
|
+
|
|
871
|
+
// OverLight config
|
|
872
|
+
overLightConfig,
|
|
873
|
+
|
|
874
|
+
// Transform calculations
|
|
875
|
+
elasticTranslation,
|
|
876
|
+
directionalScale,
|
|
877
|
+
transformStyle,
|
|
878
|
+
|
|
879
|
+
// Event handlers
|
|
880
|
+
handleMouseEnter,
|
|
881
|
+
handleMouseLeave,
|
|
882
|
+
handleMouseDown,
|
|
883
|
+
handleMouseUp,
|
|
884
|
+
handleMouseMove,
|
|
885
|
+
handleKeyDown,
|
|
70
886
|
};
|
|
71
887
|
}
|
|
888
|
+
|