@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.
Files changed (225) hide show
  1. package/README.md +19 -0
  2. package/dist/atomix.css +1703 -1544
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/index.d.ts +1465 -963
  5. package/dist/index.esm.js +16289 -25908
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +15650 -21780
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/dist/themes/applemix.css +15008 -0
  12. package/dist/themes/applemix.min.css +72 -0
  13. package/dist/themes/boomdevs.css +1608 -1450
  14. package/dist/themes/boomdevs.min.css +5 -5
  15. package/dist/themes/esrar.css +1702 -1543
  16. package/dist/themes/esrar.min.css +4 -4
  17. package/dist/themes/flashtrade.css +15159 -0
  18. package/dist/themes/flashtrade.min.css +86 -0
  19. package/dist/themes/mashroom.css +1699 -1540
  20. package/dist/themes/mashroom.min.css +7 -7
  21. package/dist/themes/shaj-default.css +1693 -1534
  22. package/dist/themes/shaj-default.min.css +4 -4
  23. package/package.json +6 -17
  24. package/src/components/Accordion/Accordion.stories.tsx +662 -21
  25. package/src/components/Accordion/Accordion.tsx +21 -14
  26. package/src/components/AtomixGlass/AtomixGlass.test.tsx +106 -72
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +529 -1195
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +400 -0
  29. package/src/components/AtomixGlass/GlassFilter.tsx +156 -0
  30. package/src/components/AtomixGlass/README.md +124 -2
  31. package/src/components/AtomixGlass/atomixGLass.old.tsx +1266 -0
  32. package/src/components/AtomixGlass/glass-utils.ts +263 -0
  33. package/src/components/AtomixGlass/shader-utils.ts +792 -68
  34. package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1250 -0
  35. package/src/components/AtomixGlass/stories/Examples.stories.tsx +5768 -0
  36. package/src/components/AtomixGlass/stories/Modes.stories.tsx +1065 -0
  37. package/src/components/AtomixGlass/stories/Playground.stories.tsx +1129 -0
  38. package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +395 -0
  39. package/src/components/AtomixGlass/stories/shared-components.tsx +301 -0
  40. package/src/components/AtomixGlass/utils.ts +3 -3
  41. package/src/components/Avatar/Avatar.tsx +3 -0
  42. package/src/components/Avatar/AvatarGroup.tsx +2 -1
  43. package/src/components/Badge/Badge.stories.tsx +76 -55
  44. package/src/components/Badge/Badge.tsx +12 -14
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +23 -4
  46. package/src/components/Button/Button.stories.tsx +501 -20
  47. package/src/components/Button/Button.tsx +5 -8
  48. package/src/components/Callout/Callout.stories.tsx +86 -35
  49. package/src/components/Callout/Callout.tsx +31 -9
  50. package/src/components/Card/Card.stories.tsx +565 -2
  51. package/src/components/Card/Card.tsx +15 -4
  52. package/src/components/Card/ElevationCard.tsx +2 -0
  53. package/src/components/Chart/AnimatedChart.tsx +179 -156
  54. package/src/components/Chart/AreaChart.tsx +123 -12
  55. package/src/components/Chart/BarChart.tsx +91 -100
  56. package/src/components/Chart/BaseChart.tsx +80 -0
  57. package/src/components/Chart/BubbleChart.tsx +114 -290
  58. package/src/components/Chart/CandlestickChart.tsx +282 -622
  59. package/src/components/Chart/Chart.stories.tsx +576 -179
  60. package/src/components/Chart/Chart.tsx +374 -75
  61. package/src/components/Chart/ChartRenderer.tsx +371 -220
  62. package/src/components/Chart/ChartToolbar.tsx +372 -61
  63. package/src/components/Chart/ChartTooltip.tsx +33 -18
  64. package/src/components/Chart/DonutChart.tsx +172 -254
  65. package/src/components/Chart/FunnelChart.tsx +169 -240
  66. package/src/components/Chart/GaugeChart.tsx +224 -392
  67. package/src/components/Chart/HeatmapChart.tsx +302 -440
  68. package/src/components/Chart/LineChart.tsx +148 -103
  69. package/src/components/Chart/MultiAxisChart.tsx +267 -395
  70. package/src/components/Chart/PieChart.tsx +114 -64
  71. package/src/components/Chart/RadarChart.tsx +202 -218
  72. package/src/components/Chart/ScatterChart.tsx +111 -97
  73. package/src/components/Chart/TreemapChart.tsx +147 -222
  74. package/src/components/Chart/WaterfallChart.tsx +253 -291
  75. package/src/components/Chart/index.ts +11 -9
  76. package/src/components/Chart/types.ts +85 -9
  77. package/src/components/Chart/utils.ts +66 -0
  78. package/src/components/ColorModeToggle/ColorModeToggle.tsx +6 -3
  79. package/src/components/Countdown/Countdown.tsx +4 -0
  80. package/src/components/DataTable/DataTable.tsx +2 -1
  81. package/src/components/DatePicker/DatePicker.stories.tsx +689 -12
  82. package/src/components/DatePicker/DatePicker.tsx +3 -9
  83. package/src/components/DatePicker/types.ts +5 -0
  84. package/src/components/Dropdown/Dropdown.stories.tsx +32 -25
  85. package/src/components/Dropdown/Dropdown.tsx +26 -28
  86. package/src/components/EdgePanel/EdgePanel.stories.tsx +473 -2
  87. package/src/components/EdgePanel/EdgePanel.tsx +101 -13
  88. package/src/components/Footer/Footer.stories.tsx +187 -60
  89. package/src/components/Footer/Footer.test.tsx +134 -0
  90. package/src/components/Footer/Footer.tsx +133 -34
  91. package/src/components/Footer/FooterLink.tsx +1 -1
  92. package/src/components/Footer/FooterSection.tsx +53 -36
  93. package/src/components/Footer/FooterSocialLink.tsx +32 -29
  94. package/src/components/Footer/README.md +82 -3
  95. package/src/components/Footer/index.ts +1 -1
  96. package/src/components/Form/Checkbox.stories.tsx +13 -5
  97. package/src/components/Form/Checkbox.tsx +3 -6
  98. package/src/components/Form/Form.stories.tsx +10 -3
  99. package/src/components/Form/Form.tsx +2 -0
  100. package/src/components/Form/FormGroup.tsx +2 -1
  101. package/src/components/Form/Input.stories.tsx +12 -11
  102. package/src/components/Form/Input.tsx +97 -95
  103. package/src/components/Form/Radio.stories.tsx +22 -7
  104. package/src/components/Form/Radio.tsx +3 -6
  105. package/src/components/Form/Select.stories.tsx +21 -6
  106. package/src/components/Form/Select.tsx +3 -5
  107. package/src/components/Form/Textarea.stories.tsx +13 -11
  108. package/src/components/Form/Textarea.tsx +88 -86
  109. package/src/components/Hero/Hero.stories.tsx +2 -3
  110. package/src/components/Hero/Hero.tsx +5 -6
  111. package/src/components/Icon/Icon.tsx +12 -1
  112. package/src/components/List/List.tsx +2 -1
  113. package/src/components/List/ListGroup.tsx +2 -1
  114. package/src/components/Messages/Messages.stories.tsx +113 -0
  115. package/src/components/Messages/Messages.tsx +52 -9
  116. package/src/components/Modal/Modal.stories.tsx +48 -32
  117. package/src/components/Modal/Modal.tsx +19 -24
  118. package/src/components/Navigation/Menu/MegaMenu.tsx +2 -2
  119. package/src/components/Navigation/Menu/Menu.tsx +2 -2
  120. package/src/components/Navigation/Nav/Nav.stories.tsx +469 -0
  121. package/src/components/Navigation/Nav/Nav.tsx +22 -4
  122. package/src/components/Navigation/Nav/NavDropdown.tsx +10 -1
  123. package/src/components/Navigation/Navbar/Navbar.stories.tsx +413 -0
  124. package/src/components/Navigation/Navbar/Navbar.tsx +70 -29
  125. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +340 -0
  126. package/src/components/Navigation/SideMenu/SideMenu.tsx +29 -2
  127. package/src/components/Pagination/Pagination.stories.tsx +13 -6
  128. package/src/components/Pagination/Pagination.tsx +7 -6
  129. package/src/components/PhotoViewer/PhotoViewer.tsx +2 -1
  130. package/src/components/Popover/Popover.stories.tsx +32 -24
  131. package/src/components/Popover/Popover.tsx +4 -1
  132. package/src/components/ProductReview/ProductReview.tsx +8 -2
  133. package/src/components/Progress/Progress.tsx +19 -3
  134. package/src/components/Rating/Rating.stories.tsx +11 -6
  135. package/src/components/Rating/Rating.tsx +3 -5
  136. package/src/components/River/River.tsx +5 -5
  137. package/src/components/SectionIntro/SectionIntro.tsx +8 -2
  138. package/src/components/Slider/Slider.stories.tsx +4 -4
  139. package/src/components/Slider/Slider.tsx +4 -3
  140. package/src/components/Spinner/Spinner.tsx +19 -3
  141. package/src/components/Steps/Steps.stories.tsx +5 -4
  142. package/src/components/Steps/Steps.tsx +8 -5
  143. package/src/components/Tab/Tab.stories.tsx +4 -3
  144. package/src/components/Tab/Tab.tsx +8 -6
  145. package/src/components/Testimonial/Testimonial.tsx +8 -2
  146. package/src/components/Todo/Todo.tsx +2 -1
  147. package/src/components/Toggle/Toggle.stories.tsx +5 -4
  148. package/src/components/Toggle/Toggle.tsx +8 -5
  149. package/src/components/Tooltip/Tooltip.stories.tsx +40 -30
  150. package/src/components/Tooltip/Tooltip.tsx +9 -2
  151. package/src/components/Upload/Upload.stories.tsx +252 -0
  152. package/src/components/Upload/Upload.tsx +92 -53
  153. package/src/components/VideoPlayer/VideoPlayer.tsx +3 -1
  154. package/src/components/index.ts +0 -4
  155. package/src/layouts/Grid/Grid.stories.tsx +10 -23
  156. package/src/layouts/Grid/Grid.tsx +20 -1
  157. package/src/layouts/Grid/GridCol.tsx +76 -48
  158. package/src/lib/composables/useAtomixGlass.ts +861 -44
  159. package/src/lib/composables/useBarChart.ts +21 -4
  160. package/src/lib/composables/useChart.ts +227 -370
  161. package/src/lib/composables/useChartExport.ts +19 -78
  162. package/src/lib/composables/useChartToolbar.ts +11 -21
  163. package/src/lib/composables/useEdgePanel.ts +125 -71
  164. package/src/lib/composables/useFooter.ts +3 -3
  165. package/src/lib/composables/useGlassContainer.ts +16 -7
  166. package/src/lib/composables/useLineChart.ts +11 -2
  167. package/src/lib/composables/usePieChart.ts +4 -14
  168. package/src/lib/composables/useRiver.ts +5 -0
  169. package/src/lib/composables/useSlider.ts +62 -24
  170. package/src/lib/composables/useVideoPlayer.ts +60 -63
  171. package/src/lib/constants/components.ts +147 -32
  172. package/src/lib/types/components.ts +355 -25
  173. package/src/lib/utils/displacement-generator.ts +55 -49
  174. package/src/lib/utils/icons.ts +1 -1
  175. package/src/lib/utils/index.ts +16 -10
  176. package/src/styles/01-settings/_settings.accordion.scss +19 -19
  177. package/src/styles/01-settings/_settings.animations.scss +5 -5
  178. package/src/styles/01-settings/_settings.avatar-group.scss +1 -1
  179. package/src/styles/01-settings/_settings.avatar.scss +17 -17
  180. package/src/styles/01-settings/_settings.background.scss +0 -3
  181. package/src/styles/01-settings/_settings.badge.scss +1 -1
  182. package/src/styles/01-settings/_settings.breadcrumb.scss +1 -1
  183. package/src/styles/01-settings/_settings.card.scss +1 -1
  184. package/src/styles/01-settings/_settings.chart.scss +65 -2
  185. package/src/styles/01-settings/_settings.dropdown.scss +1 -1
  186. package/src/styles/01-settings/_settings.edge-panel.scss +1 -1
  187. package/src/styles/01-settings/_settings.footer.scss +35 -42
  188. package/src/styles/01-settings/_settings.input.scss +1 -1
  189. package/src/styles/01-settings/_settings.list.scss +1 -1
  190. package/src/styles/01-settings/_settings.rating.scss +1 -1
  191. package/src/styles/01-settings/_settings.tabs.scss +1 -1
  192. package/src/styles/01-settings/_settings.upload.scss +6 -5
  193. package/src/styles/02-tools/_tools.animations.scss +4 -5
  194. package/src/styles/02-tools/_tools.background.scss +1 -13
  195. package/src/styles/02-tools/_tools.glass.scss +0 -1
  196. package/src/styles/02-tools/_tools.utility-api.scss +91 -48
  197. package/src/styles/03-generic/_generic.root.scss +1 -4
  198. package/src/styles/04-elements/_elements.body.scss +0 -1
  199. package/src/styles/06-components/_components.atomix-glass.scss +249 -0
  200. package/src/styles/06-components/_components.badge.scss +8 -23
  201. package/src/styles/06-components/_components.button.scss +8 -3
  202. package/src/styles/06-components/_components.callout.scss +10 -5
  203. package/src/styles/06-components/_components.card.scss +2 -14
  204. package/src/styles/06-components/_components.chart.scss +969 -1449
  205. package/src/styles/06-components/_components.dropdown.scss +19 -7
  206. package/src/styles/06-components/_components.edge-panel.scss +103 -0
  207. package/src/styles/06-components/_components.footer.scss +166 -85
  208. package/src/styles/06-components/_components.input.scss +8 -9
  209. package/src/styles/06-components/_components.list.scss +1 -0
  210. package/src/styles/06-components/_components.messages.scss +176 -0
  211. package/src/styles/06-components/_components.modal.scss +16 -4
  212. package/src/styles/06-components/_components.navbar.scss +12 -1
  213. package/src/styles/06-components/_components.side-menu.scss +5 -0
  214. package/src/styles/06-components/_components.skeleton.scss +8 -6
  215. package/src/styles/06-components/_components.upload.scss +219 -4
  216. package/src/styles/06-components/old.chart.styles.scss +1 -30
  217. package/src/styles/99-utilities/_index.scss +1 -0
  218. package/src/styles/99-utilities/_utilities.glass-fixes.scss +1 -0
  219. package/src/styles/99-utilities/_utilities.scss +1 -1
  220. package/src/components/AtomixGlass/AtomixGlass.stories.tsx +0 -3011
  221. package/src/components/AtomixGlass/AtomixGlassComprehensivePreview.stories.tsx +0 -1369
  222. package/src/components/Chart/AdvancedChart.tsx +0 -624
  223. package/src/components/Chart/LineChartNew.tsx +0 -167
  224. package/src/components/Chart/RealTimeChart.tsx +0 -436
  225. package/src/components/DatePicker/DatePicker copy.tsx +0 -551
@@ -1,4 +1,4 @@
1
- import { forwardRef, memo, useCallback, useMemo } from 'react';
1
+ import { forwardRef, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import {
3
3
  useChart,
4
4
  useChartAccessibility,
@@ -7,6 +7,16 @@ import {
7
7
  } from '../../lib/composables/useChart';
8
8
  import { CHART } from '../../lib/constants/components';
9
9
  import { ChartProps } from '../../lib/types/components';
10
+ import {
11
+ ChartDataset,
12
+ ChartScales,
13
+ ChartRenderContentParams,
14
+ ChartHandlers,
15
+ ChartAccessibility,
16
+ ChartHoveredPoint,
17
+ ChartDataPoint,
18
+ } from './types';
19
+ import { ChartContext } from './Chart';
10
20
 
11
21
  /**
12
22
  * Enhanced chart renderer component with comprehensive functionality
@@ -24,33 +34,7 @@ const ChartRenderer = memo(
24
34
  enableRealTime?: boolean;
25
35
  enableAccessibility?: boolean;
26
36
  enablePerformanceOptimization?: boolean;
27
- renderContent: (params: {
28
- scales: any;
29
- colors: string[];
30
- datasets: any[];
31
- interactionState: any;
32
- handlers: {
33
- onDataPointClick?: ChartProps['onDataPointClick'];
34
- onPointHover: (
35
- datasetIndex: number,
36
- pointIndex: number,
37
- x: number,
38
- y: number,
39
- clientX: number,
40
- clientY: number
41
- ) => void;
42
- onPointLeave: () => void;
43
- onMouseMove: (event: React.MouseEvent<SVGSVGElement>) => void;
44
- onMouseDown: (event: React.MouseEvent<SVGSVGElement>) => void;
45
- onMouseUp: () => void;
46
- onWheel: (event: React.WheelEvent<SVGSVGElement>) => void;
47
- };
48
- accessibility: {
49
- announcement: string;
50
- focusedPoint: { datasetIndex: number; pointIndex: number };
51
- getAccessibleDescription: () => string;
52
- };
53
- }) => React.ReactNode;
37
+ renderContent: (params: ChartRenderContentParams) => React.ReactNode;
54
38
  }
55
39
  >(
56
40
  (
@@ -68,116 +52,229 @@ const ChartRenderer = memo(
68
52
  },
69
53
  ref
70
54
  ) => {
71
- // Enhanced chart hooks
72
- const {
73
- calculateScales,
74
- getChartColors,
75
- interactionState,
76
- handlePointHover,
77
- handlePointLeave,
78
- handlePointClick,
79
- handleZoom,
80
- handlePan,
81
- handleDragStart,
82
- handleDragEnd,
83
- handleCrosshair,
84
- clearCrosshair,
85
- handleTouchStart,
86
- handleTouchMove,
87
- handleTouchEnd,
88
- handlePointerDown,
89
- handlePointerMove,
90
- handlePointerUp,
91
- svgRef,
92
- } = useChart({ interactive });
55
+ // Get chart context (zoom/pan state from toolbar) - optional
56
+ // Always call useContext to maintain consistent hook order
57
+ const chartContext = useContext(ChartContext);
93
58
 
59
+ // Chart composition hooks
60
+ const { calculateScales, getChartColors } = useChart();
94
61
  const { processedData, isProcessing } = useChartData(datasets, {
95
62
  enableRealTime,
96
63
  enableDecimation: enablePerformanceOptimization,
97
64
  maxDataPoints: 1000,
98
65
  });
99
-
100
- const { announcement, focusedPoint, getAccessibleDescription, handleKeyDown } =
101
- useChartAccessibility(processedData, {
102
- enableKeyboardNavigation: enableAccessibility,
103
- enableScreenReader: enableAccessibility,
104
- });
105
-
106
- const { isOptimizing, debouncedUpdate } = useChartPerformance(processedData, {
66
+ const { isOptimizing, memoizedScales } = useChartPerformance(processedData, {
67
+ enableVirtualization: false,
107
68
  enableMemoization: enablePerformanceOptimization,
108
69
  debounceMs: 100,
109
70
  });
71
+ const { announcement, focusedPoint } = useChartAccessibility(processedData, {
72
+ enableScreenReader: enableAccessibility,
73
+ enableKeyboardNavigation: enableAccessibility,
74
+ announceDataChanges: enableAccessibility,
75
+ });
76
+
77
+ // Interaction state
78
+ const interactionStateRef = useRef({
79
+ isDragging: false,
80
+ dragStart: { x: 0, y: 0 },
81
+ lastPan: { x: 0, y: 0 },
82
+ });
83
+
84
+ // Hovered point state
85
+ const [hoveredPoint, setHoveredPoint] = useState<ChartHoveredPoint | null>(null);
86
+
87
+ // Ref for SVG element and container
88
+ const svgRef = useRef<SVGSVGElement>(null);
89
+ const containerRef = useRef<HTMLDivElement>(null);
90
+ const [isInitialized, setIsInitialized] = useState(false);
91
+
92
+ // Responsive dimensions state - initialize with 0 to prevent layout shifts
93
+ const [dimensions, setDimensions] = useState({
94
+ width: 0,
95
+ height: 0,
96
+ });
97
+
98
+ // Initialize dimensions immediately when container is available
99
+ useEffect(() => {
100
+ const container = containerRef.current;
101
+ if (!container) return;
102
+
103
+ // Get initial dimensions immediately
104
+ const updateDimensions = () => {
105
+ const rect = container.getBoundingClientRect();
106
+ if (rect.width > 0 && rect.height > 0) {
107
+ setDimensions({
108
+ width: Math.floor(rect.width),
109
+ height: Math.floor(rect.height),
110
+ });
111
+ setIsInitialized(true);
112
+ }
113
+ };
114
+
115
+ // Try to get dimensions immediately
116
+ updateDimensions();
117
+
118
+ // Fallback: use requestAnimationFrame to ensure layout is complete
119
+ const rafId = requestAnimationFrame(() => {
120
+ updateDimensions();
121
+ });
122
+
123
+ // Use ResizeObserver to track container size changes
124
+ const resizeObserver = new ResizeObserver((entries) => {
125
+ for (const entry of entries) {
126
+ const { width: containerWidth, height: containerHeight } = entry.contentRect;
127
+ if (containerWidth > 0 && containerHeight > 0) {
128
+ setDimensions({
129
+ width: Math.floor(containerWidth),
130
+ height: Math.floor(containerHeight),
131
+ });
132
+ setIsInitialized(true);
133
+ }
134
+ }
135
+ });
136
+
137
+ resizeObserver.observe(container);
138
+
139
+ return () => {
140
+ cancelAnimationFrame(rafId);
141
+ resizeObserver.disconnect();
142
+ };
143
+ }, []);
144
+
145
+ // Update dimensions when props change
146
+ useEffect(() => {
147
+ if (width !== CHART.DEFAULT_WIDTH || height !== CHART.DEFAULT_HEIGHT) {
148
+ setDimensions({ width, height });
149
+ setIsInitialized(true);
150
+ }
151
+ }, [width, height]);
152
+
153
+ // Event handlers
154
+ const handlePointHover = useCallback(
155
+ (
156
+ datasetIndex: number,
157
+ pointIndex: number,
158
+ x: number,
159
+ y: number,
160
+ clientX: number,
161
+ clientY: number
162
+ ) => {
163
+ // Handle point hover
164
+ setHoveredPoint({ datasetIndex, pointIndex, x, y, clientX, clientY });
165
+ },
166
+ []
167
+ );
168
+
169
+ const handlePointLeave = useCallback(() => {
170
+ // Reset hover state
171
+ setHoveredPoint(null);
172
+ }, []);
173
+
174
+ const rafRef = useRef<number | null>(null);
110
175
 
111
- // Enhanced interaction handlers with touch/pen support
112
176
  const handleMouseMove = useCallback(
113
177
  (event: React.MouseEvent<SVGSVGElement>) => {
114
- if (!interactive || interactionState.touchState.isTouch) return;
178
+ if (!interactive || !chartContext || !chartContext.panEnabled) return;
179
+ if (!interactionStateRef.current.isDragging) return;
115
180
 
116
- const rect = event.currentTarget.getBoundingClientRect();
117
- const x = event.clientX - rect.left;
118
- const y = event.clientY - rect.top;
181
+ if (rafRef.current) return;
119
182
 
120
- handleCrosshair(x, y);
183
+ rafRef.current = requestAnimationFrame(() => {
184
+ const svg = svgRef.current;
185
+ if (!svg) {
186
+ rafRef.current = null;
187
+ return;
188
+ }
121
189
 
122
- if (interactionState.isDragging && interactionState.dragStart) {
123
- const deltaX = x - interactionState.dragStart.x;
124
- const deltaY = y - interactionState.dragStart.y;
125
- handlePan(deltaX, deltaY);
126
- }
190
+ const rect = svg.getBoundingClientRect();
191
+ const x = event.clientX - rect.left;
192
+ const y = event.clientY - rect.top;
193
+
194
+ const deltaX = x - interactionStateRef.current.dragStart.x;
195
+ const deltaY = y - interactionStateRef.current.dragStart.y;
196
+
197
+ chartContext.setPanOffset({
198
+ x: interactionStateRef.current.lastPan.x + deltaX,
199
+ y: interactionStateRef.current.lastPan.y + deltaY,
200
+ });
201
+
202
+ rafRef.current = null;
203
+ });
127
204
  },
128
- [interactive, interactionState, handleCrosshair, handlePan]
205
+ [interactive, chartContext]
129
206
  );
130
207
 
131
208
  const handleMouseDown = useCallback(
132
209
  (event: React.MouseEvent<SVGSVGElement>) => {
133
- if (!interactive || interactionState.touchState.isTouch) return;
210
+ if (!interactive || !chartContext || !chartContext.panEnabled) return;
211
+
212
+ const svg = svgRef.current;
213
+ if (!svg) return;
134
214
 
135
- const rect = event.currentTarget.getBoundingClientRect();
215
+ const rect = svg.getBoundingClientRect();
136
216
  const x = event.clientX - rect.left;
137
217
  const y = event.clientY - rect.top;
138
218
 
139
- handleDragStart(x, y);
219
+ interactionStateRef.current.isDragging = true;
220
+ interactionStateRef.current.dragStart = { x, y };
221
+ interactionStateRef.current.lastPan = { ...chartContext.panOffset };
140
222
  },
141
- [interactive, interactionState.touchState.isTouch, handleDragStart]
223
+ [interactive, chartContext]
142
224
  );
143
225
 
144
226
  const handleMouseUp = useCallback(() => {
145
- if (!interactive || interactionState.touchState.isTouch) return;
146
- handleDragEnd();
147
- }, [interactive, interactionState.touchState.isTouch, handleDragEnd]);
148
-
149
- const handleWheel = useCallback(
150
- (event: React.WheelEvent<SVGSVGElement>) => {
151
- if (!interactive) return;
152
-
153
- event.preventDefault();
154
- const rect = event.currentTarget.getBoundingClientRect();
155
- const centerX = event.clientX - rect.left;
156
- const centerY = event.clientY - rect.top;
227
+ interactionStateRef.current.isDragging = false;
228
+ }, []);
157
229
 
158
- // Detect trackpad vs mouse wheel
159
- const isTrackpad = Math.abs(event.deltaY) < 50;
160
- const sensitivity = isTrackpad ? 0.01 : 0.1;
161
- const adjustedDelta = event.deltaY * sensitivity;
162
-
163
- handleZoom(adjustedDelta, centerX, centerY);
164
- },
165
- [interactive, handleZoom]
230
+ // Memoized handlers
231
+ const handlers = useMemo<ChartHandlers>(
232
+ () => ({
233
+ onDataPointClick,
234
+ onPointHover: handlePointHover,
235
+ onPointLeave: handlePointLeave,
236
+ onMouseMove: handleMouseMove,
237
+ onMouseDown: handleMouseDown,
238
+ onMouseUp: handleMouseUp,
239
+ onWheel: () => {
240
+ // Wheel handling is done via native event listener (non-passive)
241
+ // This is kept for API compatibility but not used
242
+ },
243
+ }),
244
+ [
245
+ onDataPointClick,
246
+ handlePointHover,
247
+ handlePointLeave,
248
+ handleMouseMove,
249
+ handleMouseDown,
250
+ handleMouseUp,
251
+ ]
166
252
  );
167
253
 
168
- const enhancedOnDataPointClick = useCallback(
169
- (dataPoint: any, datasetIndex: number, pointIndex: number) => {
170
- if (!interactive) return;
171
-
172
- handlePointClick(datasetIndex, pointIndex);
173
- onDataPointClick?.(dataPoint, datasetIndex, pointIndex);
174
- },
175
- [interactive, handlePointClick, onDataPointClick]
254
+ // Memoized accessibility props
255
+ const accessibility = useMemo<ChartAccessibility>(
256
+ () => ({
257
+ announcement,
258
+ focusedPoint,
259
+ getAccessibleDescription: () => 'Chart description',
260
+ }),
261
+ [announcement, focusedPoint]
176
262
  );
177
263
 
178
- // Calculate chart data with enhanced features
264
+ const transform = useMemo(() => {
265
+ if (!chartContext) return '';
266
+ return `translate(${chartContext.panOffset.x}px, ${chartContext.panOffset.y}px) scale(${chartContext.zoomLevel})`;
267
+ }, [chartContext?.panOffset.x, chartContext?.panOffset.y, chartContext?.zoomLevel]);
268
+
269
+ // Calculate chart data with enhanced features using responsive dimensions
270
+ // This MUST be called before any early returns to maintain consistent hook order
179
271
  const chartData = useMemo(() => {
180
- const scales = calculateScales(processedData, width, height, undefined, config);
272
+ // Return null if dimensions not ready to prevent calculation with invalid dimensions
273
+ if (!isInitialized || dimensions.width === 0 || dimensions.height === 0) {
274
+ return null;
275
+ }
276
+
277
+ const scales = calculateScales(processedData, dimensions.width, dimensions.height, undefined, config);
181
278
  if (!scales) return null;
182
279
 
183
280
  const colors = getChartColors(processedData.length).filter(
@@ -187,132 +284,186 @@ const ChartRenderer = memo(
187
284
  return {
188
285
  scales,
189
286
  colors,
190
- datasets: processedData.map((dataset, i) => ({
287
+ datasets: processedData.map((dataset: ChartDataset, i) => ({
191
288
  ...dataset,
192
289
  color: dataset.color || colors[i],
193
290
  })),
194
291
  };
195
- }, [processedData, config, width, height, calculateScales, getChartColors]);
292
+ }, [processedData, config, dimensions.width, dimensions.height, isInitialized, calculateScales, getChartColors]);
293
+
294
+ useEffect(() => {
295
+ return () => {
296
+ if (rafRef.current) {
297
+ cancelAnimationFrame(rafRef.current);
298
+ }
299
+ };
300
+ }, []);
301
+
302
+ // Add native wheel event listener with non-passive option to allow preventDefault
303
+ useEffect(() => {
304
+ const svg = svgRef.current;
305
+ if (!svg || !interactive || !chartContext) return;
306
+
307
+ const handleNativeWheel = (event: WheelEvent) => {
308
+ if (chartContext.zoomLevel === undefined) return;
309
+
310
+ event.preventDefault();
311
+
312
+ const delta = -event.deltaY * 0.001;
313
+ const newZoom = Math.max(0.2, Math.min(5, chartContext.zoomLevel + delta));
314
+ chartContext.setZoomLevel(newZoom);
315
+ };
316
+
317
+ // Add event listener with { passive: false } to allow preventDefault
318
+ svg.addEventListener('wheel', handleNativeWheel, { passive: false });
319
+
320
+ return () => {
321
+ svg.removeEventListener('wheel', handleNativeWheel);
322
+ };
323
+ }, [interactive, chartContext]);
324
+
325
+ // Early returns AFTER all hooks have been called
326
+ if (isOptimizing) {
327
+ return (
328
+ <div className={`${CHART.CONTENT_CLASS} ${CHART.LOADING_CLASS}`}>
329
+ <div className={CHART.LOADING_SPINNER_CLASS}></div>
330
+ <span className={CHART.LOADING_TEXT_CLASS}>Optimizing chart...</span>
331
+ </div>
332
+ );
333
+ }
334
+
335
+ // Don't render until dimensions are initialized to prevent layout shifts
336
+ if (!isInitialized || dimensions.width === 0 || dimensions.height === 0) {
337
+ return (
338
+ <div
339
+ ref={containerRef}
340
+ className={CHART.CANVAS_CLASS}
341
+ style={{
342
+ width: '100%',
343
+ height: '100%',
344
+ minHeight: '200px',
345
+ display: 'flex',
346
+ alignItems: 'center',
347
+ justifyContent: 'center',
348
+ }}
349
+ />
350
+ );
351
+ }
196
352
 
197
353
  if (!chartData) {
198
354
  return null;
199
355
  }
200
356
 
357
+ const svgWidth = dimensions.width;
358
+ const svgHeight = dimensions.height;
359
+
201
360
  return (
202
- <svg
203
- ref={ref || svgRef}
204
- width="100%"
205
- height="100%"
206
- viewBox={`0 0 ${chartData.scales.width} ${chartData.scales.height}`}
207
- preserveAspectRatio="xMidYMid meet"
208
- className={CHART.CHART_SVG_CLASS}
209
- onMouseMove={handleMouseMove}
210
- onMouseDown={handleMouseDown}
211
- onMouseUp={handleMouseUp}
212
- onMouseLeave={() => {
213
- clearCrosshair();
214
- handleMouseUp();
215
- }}
216
- onWheel={handleWheel}
217
- onTouchStart={e => handleTouchStart(e.nativeEvent)}
218
- onTouchMove={e => handleTouchMove(e.nativeEvent)}
219
- onTouchEnd={e => handleTouchEnd(e.nativeEvent)}
220
- onPointerDown={e => handlePointerDown(e.nativeEvent)}
221
- onPointerMove={e => handlePointerMove(e.nativeEvent)}
222
- onPointerUp={e => handlePointerUp(e.nativeEvent)}
223
- tabIndex={enableAccessibility ? 0 : undefined}
224
- role={enableAccessibility ? 'img' : undefined}
225
- aria-label={enableAccessibility ? getAccessibleDescription() : undefined}
226
- onKeyDown={
227
- enableAccessibility
228
- ? e =>
229
- handleKeyDown(e.nativeEvent, (datasetIndex: number, pointIndex: number) => {
230
- const dataPoint = processedData[datasetIndex]?.data[pointIndex];
231
- enhancedOnDataPointClick(dataPoint, datasetIndex, pointIndex);
232
- })
233
- : undefined
234
- }
235
- style={{
236
- touchAction: 'none',
237
- userSelect: 'none',
238
- WebkitUserSelect: 'none',
239
- }}
240
- >
241
- {/* Accessibility announcement */}
242
- {enableAccessibility && announcement && (
243
- <text x="-9999" y="-9999" className="sr-only" aria-live="polite">
244
- {announcement}
245
- </text>
246
- )}
247
-
248
- {renderContent({
249
- ...chartData,
250
- interactionState,
251
- handlers: {
252
- onDataPointClick: enhancedOnDataPointClick,
253
- onPointHover: handlePointHover,
254
- onPointLeave: handlePointLeave,
255
- onMouseMove: handleMouseMove,
256
- onMouseDown: handleMouseDown,
257
- onMouseUp: handleMouseUp,
258
- onWheel: handleWheel,
259
- },
260
- accessibility: {
261
- announcement,
262
- focusedPoint,
263
- getAccessibleDescription,
264
- },
265
- })}
266
-
267
- {/* Crosshair overlay */}
268
- {interactive && interactionState.crosshair && (
269
- <g className="chart-crosshair" style={{ pointerEvents: 'none' }}>
270
- <line
271
- x1={interactionState.crosshair.x}
272
- y1={0}
273
- x2={interactionState.crosshair.x}
274
- y2={chartData.scales.height}
275
- stroke="var(--atomix-gray-4)"
276
- strokeWidth="1"
277
- strokeDasharray="4,4"
278
- opacity="0.6"
279
- />
280
- <line
281
- x1={0}
282
- y1={interactionState.crosshair.y}
283
- x2={chartData.scales.width}
284
- y2={interactionState.crosshair.y}
285
- stroke="var(--atomix-gray-4)"
286
- strokeWidth="1"
287
- strokeDasharray="4,4"
288
- opacity="0.6"
289
- />
290
- </g>
291
- )}
292
-
293
- {/* Zoom indicator */}
294
- {interactive && interactionState.zoomLevel !== 1 && (
295
- <g className="chart-zoom-indicator">
296
- <rect
297
- x={chartData.scales.width - 80}
298
- y={10}
299
- width="70"
300
- height="20"
301
- fill="rgba(0, 0, 0, 0.8)"
302
- rx="4"
303
- />
304
- <text
305
- x={chartData.scales.width - 45}
306
- y={24}
307
- textAnchor="middle"
308
- fill="white"
309
- fontSize="12"
310
- >
311
- {Math.round(interactionState.zoomLevel * 100)}%
312
- </text>
313
- </g>
314
- )}
315
- </svg>
361
+ <>
362
+ <div ref={containerRef} className={CHART.CANVAS_CLASS} style={{ width: '100%', height: '100%' }}>
363
+ <svg
364
+ ref={svgRef}
365
+ width={svgWidth}
366
+ height={svgHeight}
367
+ viewBox={`0 0 ${svgWidth} ${svgHeight}`}
368
+ preserveAspectRatio="xMidYMid meet"
369
+ role="img"
370
+ aria-label="Chart visualization"
371
+ tabIndex={0}
372
+ style={{
373
+ width: '100%',
374
+ height: '100%',
375
+ transform: transform,
376
+ transformOrigin: 'center center',
377
+ willChange: chartContext?.panEnabled ? 'transform' : 'auto',
378
+ }}
379
+ className="c-chart__svg"
380
+ onMouseMove={handleMouseMove}
381
+ onMouseDown={handleMouseDown}
382
+ onMouseUp={handleMouseUp}
383
+ onMouseLeave={handleMouseUp}
384
+ >
385
+ {/* Chart grid */}
386
+ <g className="c-chart__grid-group">
387
+ {/* X-axis grid lines */}
388
+ {chartData.datasets[0]?.data.map((_: any, index: number) => (
389
+ <line
390
+ key={`x-grid-${index}`}
391
+ x1={chartData.scales.xScale(index, chartData.datasets[0]?.data.length)}
392
+ y1={0}
393
+ x2={chartData.scales.xScale(index, chartData.datasets[0]?.data.length)}
394
+ y2={svgHeight}
395
+ className="c-chart__grid c-chart__grid--vertical"
396
+ />
397
+ ))}
398
+
399
+ {/* Y-axis grid lines - generate 5 ticks by default */}
400
+ {Array.from({ length: 5 }).map((_: any, index: number) => {
401
+ const value =
402
+ chartData.scales.minValue +
403
+ (chartData.scales.maxValue - chartData.scales.minValue) * (index / 4);
404
+ return (
405
+ <line
406
+ key={`y-grid-${index}`}
407
+ x1={0}
408
+ y1={chartData.scales.yScale(value)}
409
+ x2={svgWidth}
410
+ y2={chartData.scales.yScale(value)}
411
+ className="c-chart__grid c-chart__grid--horizontal"
412
+ />
413
+ );
414
+ })}
415
+ </g>
416
+
417
+ {/* Render chart content */}
418
+ {renderContent({
419
+ scales: chartData.scales,
420
+ colors: chartData.colors,
421
+ datasets: chartData.datasets,
422
+ interactionState: {
423
+ hoveredIndex: hoveredPoint?.pointIndex ?? null,
424
+ selectedIndex: null,
425
+ },
426
+ handlers,
427
+ accessibility,
428
+ hoveredPoint,
429
+ toolbarState: chartContext
430
+ ? {
431
+ showTooltips: chartContext.toolbarState?.showTooltips,
432
+ showLegend: chartContext.toolbarState?.showLegend,
433
+ animationsEnabled: chartContext.toolbarState?.animationsEnabled,
434
+ showGrid: chartContext.toolbarState?.showGrid,
435
+ }
436
+ : undefined,
437
+ config,
438
+ })}
439
+
440
+ {/* Crosshair for enhanced interaction */}
441
+ {interactive && chartContext?.panEnabled && (
442
+ <g className="c-chart__crosshair">
443
+ <line
444
+ x1={0}
445
+ y1={svgHeight / 2}
446
+ x2={svgWidth}
447
+ y2={svgHeight / 2}
448
+ className="c-chart__crosshair-line c-chart__crosshair-line--horizontal"
449
+ />
450
+ <line
451
+ x1={svgWidth / 2}
452
+ y1={0}
453
+ x2={svgWidth / 2}
454
+ y2={svgHeight}
455
+ className="c-chart__crosshair-line c-chart__crosshair-line--vertical"
456
+ />
457
+ </g>
458
+ )}
459
+ </svg>
460
+ </div>
461
+
462
+ {/* Screen reader announcements */}
463
+ <div aria-live="polite" className="u-visually-hidden">
464
+ {announcement}
465
+ </div>
466
+ </>
316
467
  );
317
468
  }
318
469
  )