@dmsi/wedgekit-react 0.0.550 → 0.0.552

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 (180) hide show
  1. package/dist/{chunk-U3QGZAVS.js → chunk-JADOJNBI.js} +4 -4
  2. package/dist/{chunk-N2KPADIL.js → chunk-WNGFRQ4Y.js} +7 -7
  3. package/dist/{chunk-ZVY3TLXL.js → chunk-ZIPJMN2E.js} +4 -4
  4. package/dist/components/Alert.js +2 -2
  5. package/dist/components/CalendarRange.js +10 -10
  6. package/dist/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.js +10 -10
  7. package/dist/components/DataGrid/ColumnSelectorHeaderCell/index.js +10 -10
  8. package/dist/components/DataGrid/PinnedColumns.js +10 -10
  9. package/dist/components/DataGrid/TableBody/LoadingCell.js +10 -10
  10. package/dist/components/DataGrid/TableBody/TableBodyRow.js +10 -10
  11. package/dist/components/DataGrid/TableBody/index.js +10 -10
  12. package/dist/components/DataGrid/index.js +10 -10
  13. package/dist/components/DataGrid/utils.js +10 -10
  14. package/dist/components/DateInput.js +10 -10
  15. package/dist/components/DateRangeInput.js +10 -10
  16. package/dist/components/FilterGroup.js +5 -5
  17. package/dist/components/MobileDataGrid/ColumnSelector/index.js +10 -10
  18. package/dist/components/MobileDataGrid/MobileDataGridHeader.js +10 -10
  19. package/dist/components/MobileDataGrid/RowDetailModalProvider/index.js +5 -5
  20. package/dist/components/MobileDataGrid/index.js +10 -10
  21. package/dist/components/Modal.js +4 -4
  22. package/dist/components/ModalButtons.js +2 -2
  23. package/dist/components/ModalHeader.js +2 -2
  24. package/dist/components/NavigationTab.js +2 -2
  25. package/dist/components/NavigationTabs.js +2 -2
  26. package/dist/components/NestedMenu.js +3 -3
  27. package/dist/components/Notification.js +3 -3
  28. package/dist/components/OptionPill.js +2 -2
  29. package/dist/components/PDFViewer/DownloadIcon.js +2 -2
  30. package/dist/components/PDFViewer/PDFNavigation.js +2 -2
  31. package/dist/components/PDFViewer/index.js +6 -6
  32. package/dist/components/ProductImagePreview/index.js +1 -1
  33. package/dist/components/Stepper.js +3 -3
  34. package/dist/components/Toast.js +3 -3
  35. package/dist/components/Upload.js +3 -3
  36. package/dist/components/index.js +16 -16
  37. package/package.json +8 -9
  38. package/src/brand.css +0 -125
  39. package/src/classNames.ts +0 -174
  40. package/src/components/AccessChangerTabItem.tsx +0 -71
  41. package/src/components/Accordion.tsx +0 -108
  42. package/src/components/Alert.tsx +0 -81
  43. package/src/components/Breadcrumbs.tsx +0 -142
  44. package/src/components/Button.tsx +0 -216
  45. package/src/components/CalendarRange.tsx +0 -628
  46. package/src/components/Caption.tsx +0 -144
  47. package/src/components/Card.tsx +0 -88
  48. package/src/components/Checkbox.tsx +0 -206
  49. package/src/components/CompactImagesPreview.tsx +0 -135
  50. package/src/components/ContentTab.tsx +0 -84
  51. package/src/components/ContentTabs.tsx +0 -136
  52. package/src/components/DMSiLogo.tsx +0 -33
  53. package/src/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.tsx +0 -35
  54. package/src/components/DataGrid/ColumnSelectorHeaderCell/index.tsx +0 -74
  55. package/src/components/DataGrid/PinnedColumns.tsx +0 -183
  56. package/src/components/DataGrid/TableBody/LoadingCell.tsx +0 -44
  57. package/src/components/DataGrid/TableBody/TableBodyRow.tsx +0 -157
  58. package/src/components/DataGrid/TableBody/index.tsx +0 -185
  59. package/src/components/DataGrid/index.tsx +0 -756
  60. package/src/components/DataGrid/types.ts +0 -98
  61. package/src/components/DataGrid/utils.tsx +0 -15
  62. package/src/components/DataGridCell.tsx +0 -526
  63. package/src/components/DataTable.tsx +0 -881
  64. package/src/components/DateInput.tsx +0 -306
  65. package/src/components/DateRangeInput.tsx +0 -758
  66. package/src/components/DebugJson.tsx +0 -28
  67. package/src/components/Display.tsx +0 -66
  68. package/src/components/EditingContext.tsx +0 -43
  69. package/src/components/EmptyCartIcon.tsx +0 -18
  70. package/src/components/FilterGroup.tsx +0 -264
  71. package/src/components/FullViewportBox.tsx +0 -19
  72. package/src/components/Grid.tsx +0 -97
  73. package/src/components/Heading.tsx +0 -72
  74. package/src/components/HorizontalDivider.tsx +0 -22
  75. package/src/components/Icon.tsx +0 -39
  76. package/src/components/ImagePlaceholder.tsx +0 -22
  77. package/src/components/Input.tsx +0 -609
  78. package/src/components/InputGroup.tsx +0 -59
  79. package/src/components/Label.tsx +0 -46
  80. package/src/components/Link.tsx +0 -117
  81. package/src/components/List.tsx +0 -18
  82. package/src/components/ListGroup.tsx +0 -82
  83. package/src/components/LiveChatComponent.tsx +0 -56
  84. package/src/components/LoadingScrim.tsx +0 -33
  85. package/src/components/LogoAgilityTopBar.tsx +0 -54
  86. package/src/components/LogoDMSiTopBar.tsx +0 -33
  87. package/src/components/LogoMillworkTopBar.tsx +0 -119
  88. package/src/components/MainBar.tsx +0 -91
  89. package/src/components/MaxViewportBox.tsx +0 -19
  90. package/src/components/Menu.tsx +0 -316
  91. package/src/components/MenuOption.tsx +0 -330
  92. package/src/components/MobileDataGrid/ColumnList.tsx +0 -66
  93. package/src/components/MobileDataGrid/ColumnSelector/index.tsx +0 -97
  94. package/src/components/MobileDataGrid/GridContextProvider/GridContext.tsx +0 -25
  95. package/src/components/MobileDataGrid/GridContextProvider/index.tsx +0 -132
  96. package/src/components/MobileDataGrid/GridContextProvider/useGridContext.ts +0 -10
  97. package/src/components/MobileDataGrid/MobileDataGridCard/MobileDataGridColumn.tsx +0 -27
  98. package/src/components/MobileDataGrid/MobileDataGridCard/index.tsx +0 -138
  99. package/src/components/MobileDataGrid/MobileDataGridHeader.tsx +0 -81
  100. package/src/components/MobileDataGrid/RowDetailModalProvider/ModalContent.tsx +0 -42
  101. package/src/components/MobileDataGrid/RowDetailModalProvider/index.tsx +0 -68
  102. package/src/components/MobileDataGrid/dataGridReducer.ts +0 -55
  103. package/src/components/MobileDataGrid/index.tsx +0 -92
  104. package/src/components/MobileDataGrid/types.ts +0 -4
  105. package/src/components/Modal.tsx +0 -312
  106. package/src/components/ModalButtons.tsx +0 -62
  107. package/src/components/ModalContent.tsx +0 -31
  108. package/src/components/ModalHeader.tsx +0 -78
  109. package/src/components/ModalScrim.tsx +0 -42
  110. package/src/components/NavigationTab.tsx +0 -95
  111. package/src/components/NavigationTabs.tsx +0 -70
  112. package/src/components/NestedMenu.tsx +0 -131
  113. package/src/components/Notification.tsx +0 -128
  114. package/src/components/OptionPill.tsx +0 -139
  115. package/src/components/OrderCheckIcon.tsx +0 -19
  116. package/src/components/PDFViewer/DownloadIcon.tsx +0 -25
  117. package/src/components/PDFViewer/PDFElement.tsx +0 -90
  118. package/src/components/PDFViewer/PDFNavigation.tsx +0 -68
  119. package/src/components/PDFViewer/PDFPage.tsx +0 -34
  120. package/src/components/PDFViewer/index.tsx +0 -128
  121. package/src/components/Pagination.tsx +0 -182
  122. package/src/components/Paragraph.tsx +0 -55
  123. package/src/components/Password.tsx +0 -62
  124. package/src/components/ProductImagePreview/CarouselPagination.tsx +0 -54
  125. package/src/components/ProductImagePreview/MobileImageCarousel.tsx +0 -226
  126. package/src/components/ProductImagePreview/ProductPrimaryImage.tsx +0 -219
  127. package/src/components/ProductImagePreview/Thumbnail.tsx +0 -55
  128. package/src/components/ProductImagePreview/ZoomWindow.tsx +0 -136
  129. package/src/components/ProductImagePreview/index.tsx +0 -182
  130. package/src/components/ProductImagePreview/useProductImagePreview.ts +0 -211
  131. package/src/components/ProjectBar.tsx +0 -82
  132. package/src/components/Radio.tsx +0 -146
  133. package/src/components/Search.tsx +0 -152
  134. package/src/components/SearchResultImage/index.tsx +0 -39
  135. package/src/components/Select.tsx +0 -114
  136. package/src/components/SideMenu.tsx +0 -30
  137. package/src/components/SideMenuGroup.tsx +0 -95
  138. package/src/components/SideMenuItem.tsx +0 -109
  139. package/src/components/SimpleTable.tsx +0 -77
  140. package/src/components/SkeletonParagraph.tsx +0 -31
  141. package/src/components/Spinner.tsx +0 -32
  142. package/src/components/Stack.tsx +0 -347
  143. package/src/components/StatusPill.tsx +0 -59
  144. package/src/components/Stepper.tsx +0 -128
  145. package/src/components/Subheader.tsx +0 -50
  146. package/src/components/Surface.tsx +0 -37
  147. package/src/components/Swatch.tsx +0 -1341
  148. package/src/components/Textarea.tsx +0 -102
  149. package/src/components/Theme.tsx +0 -27
  150. package/src/components/Time.tsx +0 -460
  151. package/src/components/Toast.tsx +0 -268
  152. package/src/components/Tooltip.tsx +0 -159
  153. package/src/components/TopBar.tsx +0 -139
  154. package/src/components/Upload.tsx +0 -107
  155. package/src/components/WorldpayIframe.tsx +0 -7
  156. package/src/components/index.ts +0 -34
  157. package/src/components/useMenuSystem.tsx +0 -456
  158. package/src/components/useMounted.tsx +0 -14
  159. package/src/darkmode.css +0 -278
  160. package/src/fonts.css +0 -23
  161. package/src/hooks/index.ts +0 -4
  162. package/src/hooks/useInfiniteScroll.tsx +0 -40
  163. package/src/hooks/useKeydown.ts +0 -42
  164. package/src/hooks/useMatchesMedia.ts +0 -18
  165. package/src/hooks/useTableLayout.ts +0 -106
  166. package/src/index.css +0 -800
  167. package/src/index.tsx +0 -5
  168. package/src/types.ts +0 -150
  169. package/src/utils/date.ts +0 -236
  170. package/src/utils/formatting.tsx +0 -81
  171. package/src/utils/index.ts +0 -4
  172. package/src/utils/mergeObjectArrays.ts +0 -18
  173. package/src/utils.ts +0 -24
  174. package/dist/{chunk-7FQ7PGUF.js → chunk-7COWXCPA.js} +3 -3
  175. package/dist/{chunk-NKCFYM7A.js → chunk-7SFFUICM.js} +3 -3
  176. package/dist/{chunk-25RZP3VR.js → chunk-AKJUBFJK.js} +3 -3
  177. package/dist/{chunk-TAPYQBQU.js → chunk-CMMQTIVM.js} +3 -3
  178. package/dist/{chunk-GYEXSNFP.js → chunk-FWCVZWE6.js} +3 -3
  179. package/dist/{chunk-MV6W7OMC.js → chunk-QMMPHXVE.js} +3 -3
  180. package/dist/{chunk-GG5OZTI5.js → chunk-XRE52QTN.js} +3 -3
@@ -1,226 +0,0 @@
1
- import { useRef, useEffect, useState, useCallback } from "react";
2
- import type { ProductPreviewImage } from "./index";
3
-
4
- export type MobileImageCarouselProps = {
5
- images: ProductPreviewImage[];
6
- currentIndex: number;
7
- width?: number;
8
- height?: number;
9
- onChangeIndex: (index: number) => void;
10
- className?: string;
11
- };
12
-
13
- export function MobileImageCarousel({
14
- images,
15
- currentIndex,
16
- width = 483,
17
- height = 483,
18
- onChangeIndex,
19
- className = "",
20
- }: MobileImageCarouselProps) {
21
- const containerRef = useRef<HTMLDivElement>(null);
22
- const [isDragging, setIsDragging] = useState(false);
23
- const [startX, setStartX] = useState(0);
24
- const [currentTranslate, setCurrentTranslate] = useState(0);
25
- const [prevTranslate, setPrevTranslate] = useState(0);
26
- const [containerWidth, setContainerWidth] = useState(width);
27
-
28
- const imageSize = Math.min(containerWidth * 0.6, height * 0.6);
29
- const gap = 16;
30
-
31
- // Calculate translate position to center current image in the reel
32
- const getTranslateX = useCallback(
33
- (index: number) => {
34
- const containerCenter = containerWidth / 2;
35
- const imageCenter = imageSize / 2;
36
- const totalOffset = index * (imageSize + gap);
37
- return containerCenter - imageCenter - totalOffset;
38
- },
39
- [containerWidth, imageSize, gap],
40
- );
41
-
42
- useEffect(() => {
43
- const translateX = getTranslateX(currentIndex);
44
- setCurrentTranslate(translateX);
45
- setPrevTranslate(translateX);
46
- }, [currentIndex, getTranslateX]);
47
-
48
- // Update container width based on actual DOM element
49
- useEffect(() => {
50
- const updateContainerWidth = () => {
51
- if (containerRef.current) {
52
- const rect = containerRef.current.getBoundingClientRect();
53
- setContainerWidth(rect.width);
54
- }
55
- };
56
-
57
- updateContainerWidth();
58
-
59
- const resizeObserver = new ResizeObserver(updateContainerWidth);
60
- if (containerRef.current) {
61
- resizeObserver.observe(containerRef.current);
62
- }
63
-
64
- return () => resizeObserver.disconnect();
65
- }, []);
66
-
67
- // Handle touch/mouse events for swiping
68
- const handleStart = useCallback((clientX: number) => {
69
- setIsDragging(true);
70
- setStartX(clientX);
71
- }, []);
72
-
73
- const handleMove = useCallback(
74
- (clientX: number) => {
75
- if (!isDragging) return;
76
-
77
- const currentPosition = clientX;
78
- const diff = currentPosition - startX;
79
- setCurrentTranslate(prevTranslate + diff);
80
- },
81
- [isDragging, startX, prevTranslate],
82
- );
83
-
84
- const handleEnd = useCallback(() => {
85
- if (!isDragging) return;
86
-
87
- setIsDragging(false);
88
-
89
- // Determine swipe direction and snap to nearest image
90
- const moved = currentTranslate - prevTranslate;
91
- const threshold = imageSize / 3;
92
-
93
- let newIndex = currentIndex;
94
-
95
- if (moved > threshold && currentIndex > 0) {
96
- newIndex = currentIndex - 1;
97
- } else if (moved < -threshold && currentIndex < images.length - 1) {
98
- newIndex = currentIndex + 1;
99
- }
100
-
101
- if (newIndex !== currentIndex) {
102
- onChangeIndex(newIndex);
103
- } else {
104
- setCurrentTranslate(prevTranslate);
105
- }
106
- }, [
107
- isDragging,
108
- currentTranslate,
109
- prevTranslate,
110
- currentIndex,
111
- imageSize,
112
- images.length,
113
- onChangeIndex,
114
- ]);
115
-
116
- const handleMouseDown = (e: React.MouseEvent) => {
117
- e.preventDefault();
118
- handleStart(e.clientX);
119
- };
120
-
121
- const handleMouseMove = (e: React.MouseEvent) => {
122
- e.preventDefault();
123
- handleMove(e.clientX);
124
- };
125
-
126
- const handleMouseUp = () => {
127
- handleEnd();
128
- };
129
-
130
- const handleMouseLeave = () => {
131
- handleEnd();
132
- };
133
-
134
- const handleTouchStart = (e: React.TouchEvent) => {
135
- handleStart(e.touches[0].clientX);
136
- };
137
-
138
- const handleTouchMove = (e: React.TouchEvent) => {
139
- handleMove(e.touches[0].clientX);
140
- };
141
-
142
- const handleTouchEnd = () => {
143
- handleEnd();
144
- };
145
-
146
- const handleImageClick = useCallback(
147
- (index: number) => {
148
- if (!isDragging && index !== currentIndex) {
149
- onChangeIndex(index);
150
- }
151
- },
152
- [isDragging, currentIndex, onChangeIndex],
153
- );
154
-
155
- if (!images.length) return null;
156
-
157
- return (
158
- <div className={`md:hidden w-full ${className}`}>
159
- <div
160
- ref={containerRef}
161
- className="relative overflow-hidden cursor-grab active:cursor-grabbing select-none w-full"
162
- style={{
163
- height: imageSize,
164
- }}
165
- onMouseDown={handleMouseDown}
166
- onMouseMove={handleMouseMove}
167
- onMouseUp={handleMouseUp}
168
- onMouseLeave={handleMouseLeave}
169
- onTouchStart={handleTouchStart}
170
- onTouchMove={handleTouchMove}
171
- onTouchEnd={handleTouchEnd}
172
- >
173
- <div
174
- className="flex items-center"
175
- style={{
176
- transform: `translateX(${currentTranslate}px)`,
177
- transition: isDragging ? "none" : "transform 0.3s ease-out",
178
- gap: `${gap}px`,
179
- }}
180
- >
181
- {images.map((image, index) => {
182
- const isActive = index === currentIndex;
183
- const distance = Math.abs(index - currentIndex);
184
-
185
- // Only render visible images for performance
186
- const shouldRender = distance <= 2;
187
-
188
- return (
189
- <div
190
- key={image.src + index}
191
- className="flex-shrink-0 transition-opacity duration-300"
192
- style={{
193
- width: imageSize,
194
- height: imageSize,
195
- opacity: isActive ? 1 : Math.max(0.3, 1 - distance * 0.3),
196
- }}
197
- onClick={() => handleImageClick(index)}
198
- >
199
- {shouldRender ? (
200
- <img
201
- src={image.src}
202
- alt={image.alt || `Product image ${index + 1}`}
203
- className="w-full h-full object-cover"
204
- draggable={false}
205
- loading={distance <= 1 ? "eager" : "lazy"}
206
- style={{
207
- aspectRatio: "1 / 1",
208
- }}
209
- />
210
- ) : (
211
- <div
212
- className="w-full h-full bg-neutral-100 rounded-md border border-gray-200"
213
- style={{
214
- aspectRatio: "1 / 1",
215
- }}
216
- aria-hidden="true"
217
- />
218
- )}
219
- </div>
220
- );
221
- })}
222
- </div>
223
- </div>
224
- </div>
225
- );
226
- }
@@ -1,219 +0,0 @@
1
- import { useRef, useState, useCallback, useMemo, useEffect } from "react";
2
- import type { CSSProperties } from "react";
3
- import type { ProductPreviewImage } from "./index";
4
-
5
- const placeholderImageUri = "/placeholder.svg";
6
-
7
- export type ProductPrimaryImageProps = {
8
- image?: ProductPreviewImage;
9
- width: number;
10
- height: number;
11
- zoomEnabled?: boolean;
12
- zoomLensSize?: number;
13
- scrollToZoomEnabled?: boolean;
14
- className?: string;
15
- isPlaceholder?: boolean;
16
- onZoomPositionChange?: (
17
- data: {
18
- x: number;
19
- y: number;
20
- w: number;
21
- h: number;
22
- lensSize: number;
23
- } | null,
24
- active: boolean,
25
- ) => void;
26
- onScrollZoom?: (delta: number) => void;
27
- };
28
-
29
- /** Primary product image with optional zoom lens that tracks cursor movement */
30
- export function ProductPrimaryImage({
31
- image,
32
- width,
33
- height,
34
- zoomEnabled = false,
35
- zoomLensSize = 140,
36
- scrollToZoomEnabled = false,
37
- className = "",
38
- isPlaceholder = false,
39
- onZoomPositionChange,
40
- onScrollZoom,
41
- }: ProductPrimaryImageProps) {
42
- const containerRef = useRef<HTMLDivElement | null>(null);
43
- const lastPointRef = useRef<{ x: number; y: number } | null>(null);
44
- const rafRef = useRef<number | null>(null);
45
- const [active, setActive] = useState(false);
46
- const [, forceRerender] = useState(0);
47
-
48
- // Memoize image src to prevent unnecessary re-requests
49
- const imageSrc = useMemo(() => image?.src, [image?.src]);
50
-
51
- const schedule = () => {
52
- if (rafRef.current != null) return;
53
- rafRef.current = requestAnimationFrame(() => {
54
- rafRef.current = null;
55
- forceRerender((n) => n + 1);
56
- });
57
- };
58
-
59
- const handlePointerEnter = useCallback(() => {
60
- if (!zoomEnabled) return;
61
- setActive(true);
62
- const el = containerRef.current;
63
- if (el) {
64
- const r = el.getBoundingClientRect();
65
- const pt = lastPointRef.current;
66
- onZoomPositionChange?.(
67
- pt ? { ...pt, w: r.width, h: r.height, lensSize: zoomLensSize } : null,
68
- true,
69
- );
70
- }
71
- }, [zoomEnabled, onZoomPositionChange, zoomLensSize]);
72
-
73
- const handlePointerLeave = useCallback(() => {
74
- if (!zoomEnabled) return;
75
- setActive(false);
76
- lastPointRef.current = null;
77
- onZoomPositionChange?.(null, false);
78
- }, [zoomEnabled, onZoomPositionChange]);
79
-
80
- const handlePointerMove = useCallback<
81
- React.PointerEventHandler<HTMLDivElement>
82
- >(
83
- (e) => {
84
- if (isPlaceholder) return;
85
- if (!zoomEnabled || !active) return;
86
-
87
- // Ignore touch events to prevent zoom on mobile
88
- if (e.pointerType === "touch") return;
89
-
90
- const el = containerRef.current;
91
- if (!el) return;
92
- const rect = el.getBoundingClientRect();
93
- const rawX = (e.clientX - rect.left) / rect.width;
94
- const rawY = (e.clientY - rect.top) / rect.height;
95
- const size = zoomLensSize ?? 140;
96
- // Constrain lens position within image boundaries
97
- const left = Math.max(
98
- 0,
99
- Math.min(rect.width - size, rawX * rect.width - size / 2),
100
- );
101
- const top = Math.max(
102
- 0,
103
- Math.min(rect.height - size, rawY * rect.height - size / 2),
104
- );
105
- // Convert pixel position back to normalized coordinates
106
- const centerXNorm = (left + size / 2) / rect.width;
107
- const centerYNorm = (top + size / 2) / rect.height;
108
- lastPointRef.current = {
109
- x: centerXNorm,
110
- y: centerYNorm,
111
- };
112
- schedule();
113
- onZoomPositionChange?.(
114
- lastPointRef.current
115
- ? {
116
- ...lastPointRef.current,
117
- w: rect.width,
118
- h: rect.height,
119
- lensSize: zoomLensSize,
120
- }
121
- : null,
122
- true,
123
- );
124
- },
125
- [isPlaceholder, zoomEnabled, active, zoomLensSize, onZoomPositionChange],
126
- );
127
-
128
- // Set up non-passive wheel event listener to properly prevent default
129
- useEffect(() => {
130
- const container = containerRef.current;
131
- if (!container || !scrollToZoomEnabled) return;
132
-
133
- const handleNativeWheel = (e: WheelEvent) => {
134
- e.preventDefault();
135
- e.stopPropagation();
136
-
137
- if (!zoomEnabled || !active) return;
138
-
139
- // Calculate zoom delta based on wheel direction
140
- const delta = e.deltaY > 0 ? -0.2 : 0.2; // Zoom out on scroll down, zoom in on scroll up
141
- onScrollZoom?.(delta);
142
- };
143
-
144
- container.addEventListener("wheel", handleNativeWheel, { passive: false });
145
-
146
- return () => {
147
- container.removeEventListener("wheel", handleNativeWheel);
148
- };
149
- }, [scrollToZoomEnabled, zoomEnabled, active, onScrollZoom]);
150
-
151
- const handleImgError = useCallback(
152
- (e: React.SyntheticEvent<HTMLImageElement>) => {
153
- if (!placeholderImageUri) return;
154
- const img = e.currentTarget;
155
- // Prevent infinite loop if placeholder also 404s
156
- if (img.src === placeholderImageUri) return;
157
- img.src = placeholderImageUri;
158
- },
159
- [],
160
- );
161
-
162
- const pt = lastPointRef.current;
163
- let lensStyle: CSSProperties | undefined;
164
- if (pt && active && zoomEnabled) {
165
- const size = zoomLensSize;
166
- const leftRaw = pt.x * width - size / 2;
167
- const topRaw = pt.y * height - size / 2;
168
- // Position lens overlay with boundary constraints
169
- lensStyle = {
170
- width: size,
171
- height: size,
172
- left: Math.max(0, Math.min(width - size, leftRaw)),
173
- top: Math.max(0, Math.min(height - size, topRaw)),
174
- };
175
- }
176
-
177
- return (
178
- <div
179
- ref={containerRef}
180
- className={[
181
- "relative overflow-hidden bg-white rounded flex items-center justify-center select-none",
182
- zoomEnabled ? "cursor-crosshair" : "",
183
- className,
184
- ].join(" ")}
185
- style={{
186
- maxWidth: width,
187
- maxHeight: height,
188
- minWidth: width,
189
- minHeight: height,
190
- aspectRatio: "1 / 1",
191
- }}
192
- onPointerEnter={handlePointerEnter}
193
- onPointerLeave={handlePointerLeave}
194
- onPointerMove={handlePointerMove}
195
- >
196
- <img
197
- key={imageSrc}
198
- src={imageSrc ?? placeholderImageUri}
199
- alt={image?.alt || "Product image"}
200
- className="object-cover min-w-full min-h-full select-none"
201
- draggable={false}
202
- loading="lazy"
203
- onError={handleImgError}
204
- />
205
- {zoomEnabled && active && lensStyle && (
206
- <div
207
- aria-hidden
208
- className="absolute pointer-events-none border border-white/70 shadow-[0_0_0_1px_rgba(0,0,0,0.15)] rounded-md bg-white/10 backdrop-blur-[1px]"
209
- style={lensStyle}
210
- />
211
- )}
212
- {/* {scrollToZoomEnabled && zoomEnabled && active && (
213
- <div className="absolute top-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded pointer-events-none">
214
- Scroll to zoom
215
- </div>
216
- )} */}
217
- </div>
218
- );
219
- }
@@ -1,55 +0,0 @@
1
- import { useState } from "react";
2
- import { ImagePlaceholder } from "../ImagePlaceholder";
3
-
4
- export type ThumbnailProps = {
5
- width?: number;
6
- height?: number;
7
- src: string;
8
- alt?: string;
9
- isActive?: boolean;
10
- isPlaceholder?: boolean;
11
- onClick?: () => void;
12
- };
13
-
14
- export function Thumbnail({
15
- width,
16
- height,
17
- src,
18
- alt,
19
- isActive,
20
- onClick,
21
- isPlaceholder = false,
22
- }: ThumbnailProps) {
23
- const [imageError, setImageError] = useState(false);
24
-
25
- return (
26
- <button
27
- type="button"
28
- onClick={onClick}
29
- className={[
30
- "cursor-pointer relative overflow-hidden rounded aspect-square w-full", // base radius, square when no explicit size
31
- "focus:outline-none",
32
- isActive &&
33
- !isPlaceholder &&
34
- "ring-[3px] ring-offset-1 ring-border-action-normal ring-offset-white opacity-70",
35
- ].join(" ")}
36
- style={{ maxWidth: width, maxHeight: height, aspectRatio: "1 / 1" }}
37
- aria-pressed={isActive && !isPlaceholder ? "true" : "false"}
38
- >
39
- {isPlaceholder ? (
40
- <ImagePlaceholder width={115} height={115} />
41
- ) : (
42
- <img
43
- src={imageError ? "/placeholder.svg" : src}
44
- alt={alt}
45
- className="object-cover w-full h-full select-none"
46
- draggable={false}
47
- loading="lazy"
48
- onError={() => {
49
- setImageError(true);
50
- }}
51
- />
52
- )}
53
- </button>
54
- );
55
- }
@@ -1,136 +0,0 @@
1
- import type { CSSProperties } from "react";
2
- import { useMemo } from "react";
3
- import { Surface } from "../Surface";
4
- import type { ProductPreviewImage } from "./index";
5
-
6
- export type ZoomWindowProps = {
7
- image?: ProductPreviewImage;
8
- width: number;
9
- height: number;
10
- pointer: {
11
- x: number;
12
- y: number;
13
- w: number;
14
- h: number;
15
- lensSize: number;
16
- } | null;
17
- active: boolean;
18
- zoomFactor?: number;
19
- className?: string;
20
- scaleFactor?: number;
21
- primaryImagePosition?: DOMRect | null;
22
- offset?: number;
23
- };
24
-
25
- /** Floating zoom window that follows the lens cursor and shows magnified image content */
26
- export function ZoomWindow({
27
- image,
28
- width,
29
- height,
30
- pointer,
31
- active,
32
- zoomFactor = 2,
33
- scaleFactor = 2,
34
- primaryImagePosition,
35
- offset = 10,
36
- className = "",
37
- }: ZoomWindowProps) {
38
- // Memoize image src to prevent unnecessary re-requests
39
- const imageSrc = useMemo(() => image?.src, [image?.src]);
40
-
41
- if (!image || !active || !pointer) return null;
42
-
43
- const zoomWindowSize = pointer.lensSize * scaleFactor;
44
-
45
- // Calculate image scaling and positioning for zoom effect
46
- const baseW = pointer.w || width;
47
- const baseH = pointer.h || height;
48
- const scaledW = baseW * zoomFactor;
49
- const scaledH = baseH * zoomFactor;
50
- const centerX = pointer.x * baseW * zoomFactor;
51
- const centerY = pointer.y * baseH * zoomFactor;
52
- const imageOffsetX = zoomWindowSize / 2 - centerX;
53
- const imageOffsetY = zoomWindowSize / 2 - centerY;
54
-
55
- // Calculate lens position and smart window positioning
56
- const calculatePosition = (): CSSProperties => {
57
- if (!primaryImagePosition) {
58
- return {
59
- position: "fixed",
60
- zIndex: 999,
61
- top: "50%",
62
- left: "50%",
63
- transform: "translate(-50%, -50%)",
64
- };
65
- }
66
-
67
- const lensLeft =
68
- primaryImagePosition.left +
69
- pointer.x * primaryImagePosition.width -
70
- pointer.lensSize / 2;
71
- const lensTop =
72
- primaryImagePosition.top +
73
- pointer.y * primaryImagePosition.height -
74
- pointer.lensSize / 2;
75
-
76
- let left = lensLeft + pointer.lensSize + offset;
77
- let top = lensTop;
78
-
79
- const { innerWidth, innerHeight } = window;
80
-
81
- // Smart positioning: adjust if exceeds viewport bounds
82
- if (left + zoomWindowSize > innerWidth) {
83
- left = lensLeft - zoomWindowSize - offset;
84
- }
85
-
86
- if (top + zoomWindowSize > innerHeight) {
87
- top = lensTop + pointer.lensSize - zoomWindowSize;
88
- }
89
-
90
- if (top < 0) {
91
- top = lensTop;
92
- }
93
-
94
- if (left < 0) {
95
- left = lensLeft + pointer.lensSize + offset;
96
- }
97
-
98
- return {
99
- position: "fixed",
100
- zIndex: 999,
101
- left,
102
- top,
103
- };
104
- };
105
- return (
106
- <Surface
107
- elevation={16}
108
- className={className}
109
- style={{
110
- width: zoomWindowSize,
111
- height: zoomWindowSize,
112
- position: "fixed",
113
- zIndex: 999,
114
- overflow: "hidden",
115
- pointerEvents: "none",
116
- userSelect: "none",
117
- ...calculatePosition(),
118
- }}
119
- aria-hidden
120
- >
121
- <img
122
- key={imageSrc}
123
- src={imageSrc}
124
- alt=""
125
- className="pointer-events-none select-none max-w-none object-cover"
126
- style={{
127
- width: scaledW,
128
- height: scaledH,
129
- transform: `translate(${imageOffsetX}px, ${imageOffsetY}px)`,
130
- }}
131
- draggable={false}
132
- loading="lazy"
133
- />
134
- </Surface>
135
- );
136
- }