@shohojdhara/atomix 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atomix.css +0 -14
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +12 -19
- package/dist/charts.js +555 -359
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +98 -28
- package/dist/core.js +1082 -733
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +26 -21
- package/dist/forms.js +937 -350
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -256
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +518 -284
- package/dist/index.esm.js +1993 -1237
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1994 -1237
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +2 -2
- package/scripts/atomix-cli.js +43 -1
- package/scripts/cli/__tests__/utils.test.js +6 -2
- package/scripts/cli/migration-tools.js +2 -2
- package/scripts/cli/theme-bridge.js +7 -9
- package/scripts/cli/utils.js +2 -1
- package/src/components/Accordion/Accordion.stories.tsx +40 -0
- package/src/components/Accordion/Accordion.tsx +174 -56
- package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
- package/src/components/AtomixGlass/README.md +5 -5
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +185 -65
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.stories.tsx +166 -1011
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Callout/README.md +2 -2
- package/src/components/Chart/Chart.stories.tsx +1 -1
- package/src/components/Chart/Chart.tsx +5 -5
- package/src/components/Chart/TreemapChart.tsx +37 -29
- package/src/components/DatePicker/readme.md +3 -3
- package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +133 -20
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/EdgePanel/EdgePanel.tsx +164 -112
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- package/src/components/Form/Checkbox.stories.tsx +1 -1
- package/src/components/Form/Checkbox.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +1 -1
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/Radio.stories.tsx +1 -1
- package/src/components/Form/Radio.tsx +1 -1
- package/src/components/Form/Select.stories.tsx +24 -1
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +145 -94
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +39 -2
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +143 -4
- package/src/components/List/List.test.tsx +62 -0
- package/src/components/List/List.tsx +16 -5
- package/src/components/List/ListItem.tsx +20 -0
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +66 -2
- package/src/components/Modal/Modal.tsx +115 -35
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
- package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Rating/Rating.stories.tsx +1 -1
- package/src/components/Rating/Rating.test.tsx +73 -0
- package/src/components/Rating/Rating.tsx +25 -37
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Steps/Steps.stories.tsx +1 -1
- package/src/components/Steps/Steps.tsx +125 -22
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +198 -45
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- package/src/components/Todo/Todo.tsx +0 -1
- package/src/components/Toggle/Toggle.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
- package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
- package/src/lib/composables/__tests__/useChart.test.ts +50 -0
- package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
- package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
- package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
- package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
- package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
- package/src/lib/composables/glass-styles.ts +302 -0
- package/src/lib/composables/index.ts +0 -8
- package/src/lib/composables/useAtomixGlass.ts +331 -537
- package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
- package/src/lib/composables/useBarChart.ts +1 -1
- package/src/lib/composables/useBreadcrumb.ts +6 -6
- package/src/lib/composables/useChart.ts +104 -21
- package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
- package/src/lib/composables/useSlider.ts +66 -34
- package/src/lib/theme/devtools/CLI.ts +2 -10
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +21 -23
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/06-components/_components.accordion.scss +0 -2
- package/src/styles/06-components/_components.chart.scss +0 -1
- package/src/styles/06-components/_components.dropdown.scss +0 -1
- package/src/styles/06-components/_components.edge-panel.scss +0 -2
- package/src/styles/06-components/_components.photoviewer.scss +0 -1
- package/src/styles/06-components/_components.river.scss +0 -1
- package/src/styles/06-components/_components.slider.scss +0 -3
- package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { ATOMIX_GLASS } from '../constants/components';
|
|
2
|
+
import { calculateDistance, calculateElementCenter, calculateMouseInfluence, validateGlassSize, clampBlur } from '../../components/AtomixGlass/glass-utils';
|
|
3
|
+
import type { GlassSize, MousePosition, OverLightObjectConfig } from '../types/components';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Updates the styles of the AtomixGlass wrapper and container elements imperatively
|
|
7
|
+
* to avoid React re-renders on mouse movement.
|
|
8
|
+
*/
|
|
9
|
+
export const updateAtomixGlassStyles = (
|
|
10
|
+
wrapperElement: HTMLElement | null,
|
|
11
|
+
containerElement: HTMLElement | null,
|
|
12
|
+
params: {
|
|
13
|
+
mouseOffset: MousePosition;
|
|
14
|
+
globalMousePosition: MousePosition;
|
|
15
|
+
glassSize: GlassSize;
|
|
16
|
+
isHovered: boolean;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
isOverLight: boolean;
|
|
19
|
+
baseOverLightConfig: {
|
|
20
|
+
opacity: number;
|
|
21
|
+
borderOpacity: number;
|
|
22
|
+
contrast: number;
|
|
23
|
+
brightness: number;
|
|
24
|
+
shadowIntensity: number;
|
|
25
|
+
saturationBoost: number;
|
|
26
|
+
};
|
|
27
|
+
effectiveBorderRadius: number;
|
|
28
|
+
effectiveWithoutEffects: boolean;
|
|
29
|
+
effectiveReducedMotion: boolean;
|
|
30
|
+
elasticity: number;
|
|
31
|
+
directionalScale: string;
|
|
32
|
+
onClick?: () => void;
|
|
33
|
+
withLiquidBlur?: boolean;
|
|
34
|
+
blurAmount?: number;
|
|
35
|
+
saturation?: number;
|
|
36
|
+
padding?: string;
|
|
37
|
+
}
|
|
38
|
+
) => {
|
|
39
|
+
if (!wrapperElement && !containerElement) return;
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
mouseOffset,
|
|
43
|
+
globalMousePosition,
|
|
44
|
+
glassSize,
|
|
45
|
+
isHovered,
|
|
46
|
+
isActive,
|
|
47
|
+
isOverLight,
|
|
48
|
+
baseOverLightConfig,
|
|
49
|
+
effectiveBorderRadius,
|
|
50
|
+
effectiveWithoutEffects,
|
|
51
|
+
effectiveReducedMotion,
|
|
52
|
+
elasticity,
|
|
53
|
+
directionalScale,
|
|
54
|
+
onClick,
|
|
55
|
+
withLiquidBlur,
|
|
56
|
+
blurAmount = ATOMIX_GLASS.DEFAULTS.BLUR_AMOUNT,
|
|
57
|
+
saturation = ATOMIX_GLASS.DEFAULTS.SATURATION,
|
|
58
|
+
padding = ATOMIX_GLASS.DEFAULTS.PADDING,
|
|
59
|
+
} = params;
|
|
60
|
+
|
|
61
|
+
// Calculate mouse influence
|
|
62
|
+
const mouseInfluence = calculateMouseInfluence(mouseOffset);
|
|
63
|
+
const hoverIntensity = isHovered ? 1.4 : 1;
|
|
64
|
+
const activeIntensity = isActive ? 1.6 : 1;
|
|
65
|
+
|
|
66
|
+
// Calculate dynamic OverLight config
|
|
67
|
+
const overLightConfig = {
|
|
68
|
+
opacity: baseOverLightConfig.opacity * hoverIntensity * activeIntensity,
|
|
69
|
+
contrast: Math.min(1.6, baseOverLightConfig.contrast + mouseInfluence * 0.1),
|
|
70
|
+
brightness: Math.min(1.1, baseOverLightConfig.brightness + mouseInfluence * 0.05),
|
|
71
|
+
shadowIntensity: Math.min(1.2, Math.max(0.5, baseOverLightConfig.shadowIntensity + mouseInfluence * 0.2)),
|
|
72
|
+
borderOpacity: Math.min(1.0, Math.max(0.3, baseOverLightConfig.borderOpacity + mouseInfluence * 0.1)),
|
|
73
|
+
saturationBoost: baseOverLightConfig.saturationBoost
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Calculate elastic translation
|
|
77
|
+
let elasticTranslation = { x: 0, y: 0 };
|
|
78
|
+
if (!effectiveWithoutEffects && wrapperElement) {
|
|
79
|
+
const rect = wrapperElement.getBoundingClientRect();
|
|
80
|
+
const center = calculateElementCenter(rect);
|
|
81
|
+
|
|
82
|
+
// Calculate fade in factor
|
|
83
|
+
let fadeInFactor = 0;
|
|
84
|
+
if (globalMousePosition.x && globalMousePosition.y && validateGlassSize(glassSize)) {
|
|
85
|
+
const edgeDistanceX = Math.max(0, Math.abs(globalMousePosition.x - center.x) - glassSize.width / 2);
|
|
86
|
+
const edgeDistanceY = Math.max(0, Math.abs(globalMousePosition.y - center.y) - glassSize.height / 2);
|
|
87
|
+
const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
|
|
88
|
+
fadeInFactor = edgeDistance > ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE ? 0 : 1 - edgeDistance / ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
elasticTranslation = {
|
|
92
|
+
x: (globalMousePosition.x - center.x) * elasticity * 0.1 * fadeInFactor,
|
|
93
|
+
y: (globalMousePosition.y - center.y) * elasticity * 0.1 * fadeInFactor,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const transformStyle = effectiveWithoutEffects
|
|
98
|
+
? isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)'
|
|
99
|
+
: `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
|
|
100
|
+
|
|
101
|
+
// Update Wrapper Styles (glassVars)
|
|
102
|
+
if (wrapperElement) {
|
|
103
|
+
const mx = mouseOffset.x;
|
|
104
|
+
const my = mouseOffset.y;
|
|
105
|
+
const absMx = Math.abs(mx);
|
|
106
|
+
const absMy = Math.abs(my);
|
|
107
|
+
const GRADIENT = ATOMIX_GLASS.CONSTANTS.GRADIENT;
|
|
108
|
+
|
|
109
|
+
const borderGradientAngle = GRADIENT.BASE_ANGLE + mx * GRADIENT.ANGLE_MULTIPLIER;
|
|
110
|
+
const borderStop1 = Math.max(
|
|
111
|
+
GRADIENT.BORDER_STOP_1.MIN,
|
|
112
|
+
GRADIENT.BORDER_STOP_1.BASE + my * GRADIENT.BORDER_STOP_1.MULTIPLIER
|
|
113
|
+
);
|
|
114
|
+
const borderStop2 = Math.min(
|
|
115
|
+
GRADIENT.BORDER_STOP_2.MAX,
|
|
116
|
+
GRADIENT.BORDER_STOP_2.BASE + my * GRADIENT.BORDER_STOP_2.MULTIPLIER
|
|
117
|
+
);
|
|
118
|
+
const borderOpacities = [
|
|
119
|
+
GRADIENT.BORDER_OPACITY.BASE_1 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW,
|
|
120
|
+
GRADIENT.BORDER_OPACITY.BASE_2 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH,
|
|
121
|
+
GRADIENT.BORDER_OPACITY.BASE_3 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_LOW,
|
|
122
|
+
GRADIENT.BORDER_OPACITY.BASE_4 + absMx * GRADIENT.BORDER_OPACITY.MULTIPLIER_HIGH,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const configBorderOpacity = overLightConfig.borderOpacity;
|
|
126
|
+
const whiteColor = ATOMIX_GLASS.CONSTANTS.PALETTE.WHITE;
|
|
127
|
+
const blackColor = ATOMIX_GLASS.CONSTANTS.PALETTE.BLACK;
|
|
128
|
+
|
|
129
|
+
const hoverPositions = {
|
|
130
|
+
hover1: {
|
|
131
|
+
x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_1,
|
|
132
|
+
y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_1,
|
|
133
|
+
},
|
|
134
|
+
hover2: {
|
|
135
|
+
x: GRADIENT.CENTER_POSITION + mx / GRADIENT.HOVER_POSITION.DIVISOR_2,
|
|
136
|
+
y: GRADIENT.CENTER_POSITION + my / GRADIENT.HOVER_POSITION.DIVISOR_2,
|
|
137
|
+
},
|
|
138
|
+
hover3: {
|
|
139
|
+
x: GRADIENT.CENTER_POSITION + mx * GRADIENT.HOVER_POSITION.MULTIPLIER_3,
|
|
140
|
+
y: GRADIENT.CENTER_POSITION + my * GRADIENT.HOVER_POSITION.MULTIPLIER_3,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const basePosition = {
|
|
145
|
+
x: GRADIENT.CENTER_POSITION + mx * GRADIENT.BASE_LAYER_MULTIPLIER,
|
|
146
|
+
y: GRADIENT.CENTER_POSITION + my * GRADIENT.BASE_LAYER_MULTIPLIER,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const opacityValues = {
|
|
150
|
+
hover1: isHovered || isActive ? 0.5 : 0,
|
|
151
|
+
hover2: isActive ? 0.5 : 0,
|
|
152
|
+
hover3: isHovered ? 0.4 : isActive ? 0.8 : 0,
|
|
153
|
+
base: isOverLight ? overLightConfig.opacity : 0,
|
|
154
|
+
over: isOverLight ? overLightConfig.opacity * 1.1 : 0,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const style = wrapperElement.style;
|
|
158
|
+
|
|
159
|
+
style.setProperty('--atomix-glass-transform', transformStyle || 'none');
|
|
160
|
+
|
|
161
|
+
// Gradients
|
|
162
|
+
style.setProperty('--atomix-glass-border-gradient-1', `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[0] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[1] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`);
|
|
163
|
+
style.setProperty('--atomix-glass-border-gradient-2', `linear-gradient(${borderGradientAngle}deg, rgba(${whiteColor}, 0) 0%, rgba(${whiteColor}, ${(borderOpacities[2] ?? 1) * configBorderOpacity}) ${borderStop1}%, rgba(${whiteColor}, ${(borderOpacities[3] ?? 1) * configBorderOpacity}) ${borderStop2}%, rgba(${whiteColor}, 0) 100%)`);
|
|
164
|
+
|
|
165
|
+
// Hover gradients
|
|
166
|
+
style.setProperty('--atomix-glass-hover-1-gradient', isOverLight
|
|
167
|
+
? `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.BLACK_END}%)`
|
|
168
|
+
: `radial-gradient(circle at ${hoverPositions.hover1.x}% ${hoverPositions.hover1.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_1.WHITE_STOP}%)`);
|
|
169
|
+
|
|
170
|
+
style.setProperty('--atomix-glass-hover-2-gradient', isOverLight
|
|
171
|
+
? `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.BLACK_END}%)`
|
|
172
|
+
: `radial-gradient(circle at ${hoverPositions.hover2.x}% ${hoverPositions.hover2.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_2.WHITE_STOP}%)`);
|
|
173
|
+
|
|
174
|
+
style.setProperty('--atomix-glass-hover-3-gradient', isOverLight
|
|
175
|
+
? `radial-gradient(circle at ${hoverPositions.hover3.x}% ${hoverPositions.hover3.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_START}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_STOP}%, rgba(${blackColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.BLACK_END}%)`
|
|
176
|
+
: `radial-gradient(circle at ${hoverPositions.hover3.x}% ${hoverPositions.hover3.y}%, rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_START}) 0%, rgba(${whiteColor}, 0) ${ATOMIX_GLASS.CONSTANTS.GRADIENT_OPACITY.HOVER_3.WHITE_STOP}%)`);
|
|
177
|
+
|
|
178
|
+
style.setProperty('--atomix-glass-base-gradient', isOverLight
|
|
179
|
+
? `linear-gradient(${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.ANGLE}deg, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_BASE + mx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_BASE + my * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_MULTIPLIER}) ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_MID_STOP}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_BASE + absMx * ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
|
|
180
|
+
: `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.BASE_GRADIENT.WHITE_OPACITY})`);
|
|
181
|
+
|
|
182
|
+
style.setProperty('--atomix-glass-overlay-gradient', isOverLight
|
|
183
|
+
? `radial-gradient(circle at ${basePosition.x}% ${basePosition.y}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_BASE + absMx * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_START_MULTIPLIER}) 0%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_MID}) ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_MID_STOP}%, rgba(${blackColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_BASE + absMy * ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.BLACK_END_MULTIPLIER}) 100%)`
|
|
184
|
+
: `rgba(${whiteColor}, ${ATOMIX_GLASS.CONSTANTS.OVERLAY_GRADIENT.WHITE_OPACITY})`);
|
|
185
|
+
|
|
186
|
+
// Opacities
|
|
187
|
+
style.setProperty('--atomix-glass-hover-1-opacity', opacityValues.hover1.toString());
|
|
188
|
+
style.setProperty('--atomix-glass-hover-2-opacity', opacityValues.hover2.toString());
|
|
189
|
+
style.setProperty('--atomix-glass-hover-3-opacity', opacityValues.hover3.toString());
|
|
190
|
+
style.setProperty('--atomix-glass-base-opacity', opacityValues.base.toString());
|
|
191
|
+
style.setProperty('--atomix-glass-overlay-opacity', opacityValues.over.toString());
|
|
192
|
+
style.setProperty('--atomix-glass-overlay-highlight-opacity', (opacityValues.over * ATOMIX_GLASS.CONSTANTS.OVERLAY_HIGHLIGHT.OPACITY_MULTIPLIER).toString());
|
|
193
|
+
|
|
194
|
+
// Other
|
|
195
|
+
style.setProperty('--atomix-glass-blend-mode', isOverLight ? 'multiply' : 'overlay');
|
|
196
|
+
style.setProperty('--atomix-glass-radius', `${effectiveBorderRadius}px`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Update Container Styles (containerVars)
|
|
200
|
+
if (containerElement) {
|
|
201
|
+
const mx = mouseOffset.x;
|
|
202
|
+
const my = mouseOffset.y;
|
|
203
|
+
|
|
204
|
+
// Constants for blur calculation
|
|
205
|
+
const EDGE_BLUR_MULTIPLIER = 1.25;
|
|
206
|
+
const CENTER_BLUR_MULTIPLIER = 1.1;
|
|
207
|
+
const FLOW_BLUR_MULTIPLIER = 1.2;
|
|
208
|
+
const MOUSE_INFLUENCE_BLUR_FACTOR = 0.15;
|
|
209
|
+
const EDGE_INTENSITY_MOUSE_FACTOR = 0.15;
|
|
210
|
+
const CENTER_INTENSITY_MOUSE_FACTOR = 0.1;
|
|
211
|
+
const MAX_BLUR_RELATIVE = 2;
|
|
212
|
+
|
|
213
|
+
const rect = containerElement.getBoundingClientRect();
|
|
214
|
+
|
|
215
|
+
let liquidBlur = {
|
|
216
|
+
baseBlur: blurAmount,
|
|
217
|
+
edgeBlur: blurAmount * EDGE_BLUR_MULTIPLIER,
|
|
218
|
+
centerBlur: blurAmount * CENTER_BLUR_MULTIPLIER,
|
|
219
|
+
flowBlur: blurAmount * FLOW_BLUR_MULTIPLIER,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (withLiquidBlur && rect) {
|
|
223
|
+
const mouseInfluence = calculateMouseInfluence(mouseOffset);
|
|
224
|
+
const maxBlur = blurAmount * MAX_BLUR_RELATIVE;
|
|
225
|
+
|
|
226
|
+
const baseBlur = Math.min(
|
|
227
|
+
maxBlur,
|
|
228
|
+
blurAmount + mouseInfluence * blurAmount * MOUSE_INFLUENCE_BLUR_FACTOR
|
|
229
|
+
);
|
|
230
|
+
const edgeIntensity = mouseInfluence * EDGE_INTENSITY_MOUSE_FACTOR;
|
|
231
|
+
const edgeBlur = Math.min(maxBlur, baseBlur * (0.8 + edgeIntensity * 0.4));
|
|
232
|
+
const centerIntensity = mouseInfluence * CENTER_INTENSITY_MOUSE_FACTOR;
|
|
233
|
+
const centerBlur = Math.min(maxBlur, baseBlur * (0.3 + centerIntensity * 0.3));
|
|
234
|
+
const flowBlur = Math.min(maxBlur, baseBlur * FLOW_BLUR_MULTIPLIER);
|
|
235
|
+
|
|
236
|
+
liquidBlur = {
|
|
237
|
+
baseBlur: clampBlur(baseBlur),
|
|
238
|
+
edgeBlur: clampBlur(edgeBlur),
|
|
239
|
+
centerBlur: clampBlur(centerBlur),
|
|
240
|
+
flowBlur: clampBlur(flowBlur),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Backdrop filter
|
|
245
|
+
let backdropFilterString = `blur(${blurAmount}px) saturate(${saturation}%) contrast(1.05) brightness(1.05)`;
|
|
246
|
+
|
|
247
|
+
const dynamicSaturation = saturation + (liquidBlur.baseBlur || 0) * 20;
|
|
248
|
+
const area = rect ? rect.width * rect.height : 0;
|
|
249
|
+
const areaIsLarge = area > 180000;
|
|
250
|
+
const devicePrefersPerformance = effectiveReducedMotion || effectiveWithoutEffects;
|
|
251
|
+
const useMultiPass = withLiquidBlur && !devicePrefersPerformance && !areaIsLarge;
|
|
252
|
+
|
|
253
|
+
if (useMultiPass) {
|
|
254
|
+
const weightedBlur = clampBlur(
|
|
255
|
+
liquidBlur.baseBlur * 0.4 +
|
|
256
|
+
liquidBlur.edgeBlur * 0.25 +
|
|
257
|
+
liquidBlur.centerBlur * 0.15 +
|
|
258
|
+
liquidBlur.flowBlur * 0.2
|
|
259
|
+
);
|
|
260
|
+
backdropFilterString = `blur(${weightedBlur}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig.contrast}) brightness(${overLightConfig.brightness})`;
|
|
261
|
+
} else {
|
|
262
|
+
const effectiveBlur = clampBlur(
|
|
263
|
+
Math.max(
|
|
264
|
+
liquidBlur.baseBlur,
|
|
265
|
+
liquidBlur.edgeBlur * 0.8,
|
|
266
|
+
liquidBlur.centerBlur * 1.1,
|
|
267
|
+
liquidBlur.flowBlur * 0.9
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
backdropFilterString = `blur(${effectiveBlur}px) saturate(${Math.min(dynamicSaturation, 200)}%) contrast(${overLightConfig.contrast}) brightness(${overLightConfig.brightness})`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Container variables
|
|
274
|
+
const style = containerElement.style;
|
|
275
|
+
|
|
276
|
+
style.setProperty('--atomix-glass-container-width', `${glassSize.width}`);
|
|
277
|
+
style.setProperty('--atomix-glass-container-height', `${glassSize.height}`);
|
|
278
|
+
style.setProperty('--atomix-glass-container-padding', padding);
|
|
279
|
+
style.setProperty('--atomix-glass-container-radius', `${effectiveBorderRadius}px`);
|
|
280
|
+
|
|
281
|
+
style.setProperty('--atomix-glass-container-backdrop', backdropFilterString);
|
|
282
|
+
|
|
283
|
+
// Shadows
|
|
284
|
+
style.setProperty('--atomix-glass-container-shadow', isOverLight
|
|
285
|
+
? [
|
|
286
|
+
`inset 0 1px 0 rgba(255, 255, 255, ${(0.4 + mx * 0.002) * (overLightConfig.shadowIntensity || 1)})`,
|
|
287
|
+
`inset 0 -1px 0 rgba(0, 0, 0, ${(0.2 + Math.abs(my) * 0.001) * (overLightConfig.shadowIntensity || 1)})`,
|
|
288
|
+
`inset 0 0 20px rgba(0, 0, 0, ${(0.08 + Math.abs(mx + my) * 0.001) * (overLightConfig.shadowIntensity || 1)})`,
|
|
289
|
+
`0 2px 12px rgba(0, 0, 0, ${(0.12 + Math.abs(my) * 0.002) * (overLightConfig.shadowIntensity || 1)})`,
|
|
290
|
+
].join(', ')
|
|
291
|
+
: '0 0 20px rgba(0, 0, 0, 0.15) inset, 0 4px 8px rgba(0, 0, 0, 0.08) inset');
|
|
292
|
+
|
|
293
|
+
style.setProperty('--atomix-glass-container-shadow-opacity', effectiveWithoutEffects ? '0' : '1');
|
|
294
|
+
|
|
295
|
+
style.setProperty('--atomix-glass-container-bg', isOverLight
|
|
296
|
+
? `linear-gradient(${180 + mx * 0.5}deg, rgba(255, 255, 255, 0.1) 0%, transparent 20%, transparent 80%, rgba(0, 0, 0, 0.05) 100%)`
|
|
297
|
+
: 'none');
|
|
298
|
+
|
|
299
|
+
style.setProperty('--atomix-glass-container-text-shadow', isOverLight
|
|
300
|
+
? '0px 2px 12px rgba(0, 0, 0, 0)'
|
|
301
|
+
: '0px 2px 12px rgba(0, 0, 0, 0.4)');
|
|
302
|
+
|
|
303
|
+
style.setProperty('--atomix-glass-container-box-shadow', isOverLight
|
|
304
|
+
? '0px 16px 70px rgba(0, 0, 0, 0.75)'
|
|
305
|
+
: '0px 12px 40px rgba(0, 0, 0, 0.25)');
|
|
306
|
+
}
|
|
307
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BreadcrumbItemType } from '../../components/Breadcrumb/Breadcrumb';
|
|
2
2
|
import { BREADCRUMB } from '../constants/components';
|
|
3
3
|
|
|
4
4
|
interface BreadcrumbOptions {
|
|
5
|
-
items:
|
|
5
|
+
items: BreadcrumbItemType[];
|
|
6
6
|
divider?: React.ReactNode;
|
|
7
7
|
className?: string;
|
|
8
8
|
'aria-label'?: string;
|
|
@@ -40,7 +40,7 @@ export function useBreadcrumb(initialOptions?: Partial<BreadcrumbOptions>) {
|
|
|
40
40
|
* @param isLast - Whether this is the last item
|
|
41
41
|
* @returns Class string
|
|
42
42
|
*/
|
|
43
|
-
const generateItemClass = (item:
|
|
43
|
+
const generateItemClass = (item: BreadcrumbItemType, isLast: boolean): string => {
|
|
44
44
|
return [BREADCRUMB.CLASSES.ITEM, item.active || isLast ? BREADCRUMB.CLASSES.ACTIVE : '']
|
|
45
45
|
.filter(Boolean)
|
|
46
46
|
.join(' ')
|
|
@@ -53,7 +53,7 @@ export function useBreadcrumb(initialOptions?: Partial<BreadcrumbOptions>) {
|
|
|
53
53
|
* @param isLast - Whether this is the last item
|
|
54
54
|
* @returns Whether item should be a link
|
|
55
55
|
*/
|
|
56
|
-
const isItemLink = (item:
|
|
56
|
+
const isItemLink = (item: BreadcrumbItemType, isLast: boolean): boolean => {
|
|
57
57
|
return Boolean(item.href && !item.active && !isLast);
|
|
58
58
|
};
|
|
59
59
|
|
|
@@ -62,9 +62,9 @@ export function useBreadcrumb(initialOptions?: Partial<BreadcrumbOptions>) {
|
|
|
62
62
|
* @param jsonString - JSON string of items
|
|
63
63
|
* @returns Array of breadcrumb items
|
|
64
64
|
*/
|
|
65
|
-
const parseItemsFromJson = (jsonString: string):
|
|
65
|
+
const parseItemsFromJson = (jsonString: string): BreadcrumbItemType[] => {
|
|
66
66
|
try {
|
|
67
|
-
return JSON.parse(jsonString) as
|
|
67
|
+
return JSON.parse(jsonString) as BreadcrumbItemType[];
|
|
68
68
|
} catch (error) {
|
|
69
69
|
console.error('Error parsing breadcrumb items:', error);
|
|
70
70
|
return [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { CHART } from '../constants/components';
|
|
3
|
-
import { ChartDataset, ChartProps } from '../types/components';
|
|
3
|
+
import { ChartDataset, ChartProps, ChartDataPoint } from '../types/components';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Chart interaction state interface
|
|
@@ -527,12 +527,32 @@ export function useChart(initialProps?: Partial<ChartProps>) {
|
|
|
527
527
|
): ChartScales | null => {
|
|
528
528
|
if (!datasets || datasets.length === 0) return null;
|
|
529
529
|
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
530
|
+
// Calculate total points and min/max values efficiently avoiding spread operator
|
|
531
|
+
let totalPoints = 0;
|
|
532
|
+
let minValue = Infinity;
|
|
533
|
+
let maxValue = -Infinity;
|
|
534
|
+
let hasValidData = false;
|
|
535
|
+
|
|
536
|
+
for (const dataset of datasets) {
|
|
537
|
+
if (dataset.data) {
|
|
538
|
+
totalPoints += dataset.data.length;
|
|
539
|
+
const { min, max, hasValid } = getDatasetBounds(dataset.data);
|
|
540
|
+
if (hasValid) {
|
|
541
|
+
if (min < minValue) minValue = min;
|
|
542
|
+
if (max > maxValue) maxValue = max;
|
|
543
|
+
hasValidData = true;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (totalPoints === 0) return null;
|
|
549
|
+
|
|
550
|
+
// Handle case with no valid numeric data
|
|
551
|
+
if (!hasValidData) {
|
|
552
|
+
minValue = 0;
|
|
553
|
+
maxValue = 0;
|
|
554
|
+
}
|
|
533
555
|
|
|
534
|
-
const minValue = Math.min(...allDataPoints.map(point => point.value));
|
|
535
|
-
const maxValue = Math.max(...allDataPoints.map(point => point.value));
|
|
536
556
|
const valueRange = maxValue - minValue || 1; // Avoid division by zero
|
|
537
557
|
|
|
538
558
|
// Apply padding
|
|
@@ -540,7 +560,7 @@ export function useChart(initialProps?: Partial<ChartProps>) {
|
|
|
540
560
|
const innerHeight = height - padding.top - padding.bottom;
|
|
541
561
|
|
|
542
562
|
// Create scale functions
|
|
543
|
-
const xScale = (index: number, dataLength: number =
|
|
563
|
+
const xScale = (index: number, dataLength: number = totalPoints) => {
|
|
544
564
|
if (dataLength <= 1) return padding.left + innerWidth / 2;
|
|
545
565
|
return padding.left + (index / (dataLength - 1)) * innerWidth;
|
|
546
566
|
};
|
|
@@ -641,6 +661,23 @@ export function useChartData(
|
|
|
641
661
|
realTimeInterval = 1000,
|
|
642
662
|
} = options || {};
|
|
643
663
|
|
|
664
|
+
const lastDataSignature = useRef<string>('');
|
|
665
|
+
|
|
666
|
+
// Helper to generate a signature for the dataset to detect changes
|
|
667
|
+
const getDatasetSignature = useCallback((data: ChartDataset[]) => {
|
|
668
|
+
return data
|
|
669
|
+
.map(d => {
|
|
670
|
+
// Use JSON stringify for robustness to detect any value change, including historical data
|
|
671
|
+
return `${d.label}:${JSON.stringify(d.data)}`;
|
|
672
|
+
})
|
|
673
|
+
.join('|');
|
|
674
|
+
}, []);
|
|
675
|
+
|
|
676
|
+
// Update signature when processedData changes (e.g. via props)
|
|
677
|
+
useEffect(() => {
|
|
678
|
+
lastDataSignature.current = getDatasetSignature(processedData);
|
|
679
|
+
}, [processedData, getDatasetSignature]);
|
|
680
|
+
|
|
644
681
|
// Data decimation for performance
|
|
645
682
|
const decimateData = useCallback(
|
|
646
683
|
(data: ChartDataset[], maxPoints: number) => {
|
|
@@ -677,10 +714,20 @@ export function useChartData(
|
|
|
677
714
|
const n = values.length;
|
|
678
715
|
if (n < 2) return values.map((): null => null);
|
|
679
716
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
717
|
+
let xSum = 0;
|
|
718
|
+
let ySum = 0;
|
|
719
|
+
let xySum = 0;
|
|
720
|
+
let x2Sum = 0;
|
|
721
|
+
|
|
722
|
+
for (let i = 0; i < n; i++) {
|
|
723
|
+
const val = values[i];
|
|
724
|
+
// Treat null/undefined as 0 to match original reduce behavior
|
|
725
|
+
const safeVal = typeof val === 'number' ? val : 0;
|
|
726
|
+
xSum += i;
|
|
727
|
+
ySum += safeVal;
|
|
728
|
+
xySum += i * safeVal;
|
|
729
|
+
x2Sum += i * i;
|
|
730
|
+
}
|
|
684
731
|
|
|
685
732
|
const slope = (n * xySum - xSum * ySum) / (n * x2Sum - xSum * xSum);
|
|
686
733
|
const intercept = (ySum - slope * xSum) / n;
|
|
@@ -711,11 +758,20 @@ export function useChartData(
|
|
|
711
758
|
if (!enableRealTime) return undefined;
|
|
712
759
|
|
|
713
760
|
const interval = setInterval(() => {
|
|
714
|
-
setProcessedData(prev =>
|
|
761
|
+
setProcessedData(prev => {
|
|
762
|
+
const currentSignature = getDatasetSignature(prev);
|
|
763
|
+
// Only trigger update if signature changed
|
|
764
|
+
if (currentSignature === lastDataSignature.current) {
|
|
765
|
+
return prev;
|
|
766
|
+
}
|
|
767
|
+
// Note: We do not update lastDataSignature.current here to avoid side effects in updater.
|
|
768
|
+
// It will be updated by the useEffect([processedData]) when the state update completes.
|
|
769
|
+
return [...prev];
|
|
770
|
+
});
|
|
715
771
|
}, realTimeInterval);
|
|
716
772
|
|
|
717
773
|
return () => clearInterval(interval);
|
|
718
|
-
}, [enableRealTime, realTimeInterval]);
|
|
774
|
+
}, [enableRealTime, realTimeInterval, getDatasetSignature]);
|
|
719
775
|
|
|
720
776
|
return {
|
|
721
777
|
processedData,
|
|
@@ -831,11 +887,11 @@ export function useChartAccessibility(
|
|
|
831
887
|
const datasetDescriptions = datasets
|
|
832
888
|
.map((dataset, i) => {
|
|
833
889
|
const dataCount = dataset.data?.length || 0;
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
const
|
|
890
|
+
const { min, max, hasValid } = getDatasetBounds(dataset.data);
|
|
891
|
+
const minVal = hasValid ? min : 0;
|
|
892
|
+
const maxVal = hasValid ? max : 0;
|
|
837
893
|
|
|
838
|
-
return `Dataset ${i + 1}: ${dataset.label}, ${dataCount} points, range ${
|
|
894
|
+
return `Dataset ${i + 1}: ${dataset.label}, ${dataCount} points, range ${minVal} to ${maxVal}`;
|
|
839
895
|
})
|
|
840
896
|
.join('. ');
|
|
841
897
|
|
|
@@ -878,14 +934,13 @@ export function useChartPerformance(
|
|
|
878
934
|
|
|
879
935
|
// Cache expensive scale calculations
|
|
880
936
|
return datasets.map(dataset => {
|
|
881
|
-
const
|
|
882
|
-
const validValues = values.length > 0 ? values : [0];
|
|
937
|
+
const { min, max, hasValid } = getDatasetBounds(dataset.data);
|
|
883
938
|
|
|
884
939
|
return {
|
|
885
940
|
label: dataset.label,
|
|
886
941
|
dataLength: dataset.data?.length || 0,
|
|
887
|
-
minValue:
|
|
888
|
-
maxValue:
|
|
942
|
+
minValue: hasValid ? min : 0,
|
|
943
|
+
maxValue: hasValid ? max : 0,
|
|
889
944
|
};
|
|
890
945
|
});
|
|
891
946
|
}, [datasets, enableMemoization]);
|
|
@@ -937,3 +992,31 @@ export function useChartPerformance(
|
|
|
937
992
|
getVisibleRange,
|
|
938
993
|
};
|
|
939
994
|
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Helper to calculate min/max values from a dataset efficiently
|
|
998
|
+
* avoiding spread operator which can cause stack overflow on large arrays
|
|
999
|
+
*/
|
|
1000
|
+
export function getDatasetBounds(data: ChartDataPoint[] | undefined): {
|
|
1001
|
+
min: number;
|
|
1002
|
+
max: number;
|
|
1003
|
+
hasValid: boolean;
|
|
1004
|
+
} {
|
|
1005
|
+
let min = Infinity;
|
|
1006
|
+
let max = -Infinity;
|
|
1007
|
+
let hasValid = false;
|
|
1008
|
+
|
|
1009
|
+
if (data && data.length > 0) {
|
|
1010
|
+
for (let i = 0; i < data.length; i++) {
|
|
1011
|
+
const point = data[i];
|
|
1012
|
+
if (point && typeof point.value === 'number') {
|
|
1013
|
+
const val = point.value;
|
|
1014
|
+
if (val < min) min = val;
|
|
1015
|
+
if (val > max) max = val;
|
|
1016
|
+
hasValid = true;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return { min, max, hasValid };
|
|
1022
|
+
}
|
|
@@ -55,6 +55,7 @@ export function useHeroBackgroundSlider(
|
|
|
55
55
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
56
56
|
const autoplayRef = useRef<NodeJS.Timeout | null>(null);
|
|
57
57
|
const isPausedRef = useRef(false);
|
|
58
|
+
const callbackRef = useRef<() => void>();
|
|
58
59
|
|
|
59
60
|
// Create refs for slide containers
|
|
60
61
|
const slideRefs = useMemo(
|
|
@@ -124,6 +125,15 @@ export function useHeroBackgroundSlider(
|
|
|
124
125
|
handleSlideTransition(nextIndex);
|
|
125
126
|
}, [currentIndex, slides.length, loop, handleSlideTransition]);
|
|
126
127
|
|
|
128
|
+
// Update callbackRef whenever nextSlide or isTransitioning changes
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
callbackRef.current = () => {
|
|
131
|
+
if (!isPausedRef.current && !isTransitioning) {
|
|
132
|
+
nextSlide();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}, [nextSlide, isTransitioning]);
|
|
136
|
+
|
|
127
137
|
/**
|
|
128
138
|
* Pause autoplay
|
|
129
139
|
*/
|
|
@@ -146,13 +156,13 @@ export function useHeroBackgroundSlider(
|
|
|
146
156
|
// Restart autoplay
|
|
147
157
|
if (!autoplayRef.current) {
|
|
148
158
|
autoplayRef.current = setInterval(() => {
|
|
149
|
-
if (
|
|
150
|
-
|
|
159
|
+
if (callbackRef.current) {
|
|
160
|
+
callbackRef.current();
|
|
151
161
|
}
|
|
152
162
|
}, delay);
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
|
-
}, [autoplay, slides.length
|
|
165
|
+
}, [autoplay, slides.length]);
|
|
156
166
|
|
|
157
167
|
// Autoplay effect
|
|
158
168
|
useEffect(() => {
|
|
@@ -161,7 +171,6 @@ export function useHeroBackgroundSlider(
|
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
const delay = typeof autoplay === 'object' ? autoplay.delay : 3000;
|
|
164
|
-
const pauseOnHover = typeof autoplay === 'object' ? autoplay.pauseOnHover : false;
|
|
165
174
|
|
|
166
175
|
// Clear any existing interval
|
|
167
176
|
if (autoplayRef.current) {
|
|
@@ -172,8 +181,8 @@ export function useHeroBackgroundSlider(
|
|
|
172
181
|
// Start autoplay if not paused
|
|
173
182
|
if (!isPausedRef.current) {
|
|
174
183
|
autoplayRef.current = setInterval(() => {
|
|
175
|
-
if (
|
|
176
|
-
|
|
184
|
+
if (callbackRef.current) {
|
|
185
|
+
callbackRef.current();
|
|
177
186
|
}
|
|
178
187
|
}, delay);
|
|
179
188
|
}
|
|
@@ -184,7 +193,7 @@ export function useHeroBackgroundSlider(
|
|
|
184
193
|
autoplayRef.current = null;
|
|
185
194
|
}
|
|
186
195
|
};
|
|
187
|
-
}, [autoplay, slides.length
|
|
196
|
+
}, [autoplay, slides.length]);
|
|
188
197
|
|
|
189
198
|
// Initialize first video if needed
|
|
190
199
|
useEffect(() => {
|