@dmsi/wedgekit-react 0.0.551 → 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 (137) hide show
  1. package/package.json +2 -3
  2. package/src/brand.css +0 -125
  3. package/src/classNames.ts +0 -174
  4. package/src/components/AccessChangerTabItem.tsx +0 -71
  5. package/src/components/Accordion.tsx +0 -108
  6. package/src/components/Alert.tsx +0 -81
  7. package/src/components/Breadcrumbs.tsx +0 -142
  8. package/src/components/Button.tsx +0 -216
  9. package/src/components/CalendarRange.tsx +0 -628
  10. package/src/components/Caption.tsx +0 -144
  11. package/src/components/Card.tsx +0 -88
  12. package/src/components/Checkbox.tsx +0 -206
  13. package/src/components/CompactImagesPreview.tsx +0 -135
  14. package/src/components/ContentTab.tsx +0 -84
  15. package/src/components/ContentTabs.tsx +0 -136
  16. package/src/components/DMSiLogo.tsx +0 -33
  17. package/src/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.tsx +0 -35
  18. package/src/components/DataGrid/ColumnSelectorHeaderCell/index.tsx +0 -74
  19. package/src/components/DataGrid/PinnedColumns.tsx +0 -183
  20. package/src/components/DataGrid/TableBody/LoadingCell.tsx +0 -44
  21. package/src/components/DataGrid/TableBody/TableBodyRow.tsx +0 -157
  22. package/src/components/DataGrid/TableBody/index.tsx +0 -185
  23. package/src/components/DataGrid/index.tsx +0 -756
  24. package/src/components/DataGrid/types.ts +0 -98
  25. package/src/components/DataGrid/utils.tsx +0 -15
  26. package/src/components/DataGridCell.tsx +0 -526
  27. package/src/components/DataTable.tsx +0 -881
  28. package/src/components/DateInput.tsx +0 -306
  29. package/src/components/DateRangeInput.tsx +0 -758
  30. package/src/components/DebugJson.tsx +0 -28
  31. package/src/components/Display.tsx +0 -66
  32. package/src/components/EditingContext.tsx +0 -43
  33. package/src/components/EmptyCartIcon.tsx +0 -18
  34. package/src/components/FilterGroup.tsx +0 -264
  35. package/src/components/FullViewportBox.tsx +0 -19
  36. package/src/components/Grid.tsx +0 -97
  37. package/src/components/Heading.tsx +0 -72
  38. package/src/components/HorizontalDivider.tsx +0 -22
  39. package/src/components/Icon.tsx +0 -39
  40. package/src/components/ImagePlaceholder.tsx +0 -22
  41. package/src/components/Input.tsx +0 -609
  42. package/src/components/InputGroup.tsx +0 -59
  43. package/src/components/Label.tsx +0 -46
  44. package/src/components/Link.tsx +0 -117
  45. package/src/components/List.tsx +0 -18
  46. package/src/components/ListGroup.tsx +0 -82
  47. package/src/components/LiveChatComponent.tsx +0 -56
  48. package/src/components/LoadingScrim.tsx +0 -33
  49. package/src/components/LogoAgilityTopBar.tsx +0 -54
  50. package/src/components/LogoDMSiTopBar.tsx +0 -33
  51. package/src/components/LogoMillworkTopBar.tsx +0 -119
  52. package/src/components/MainBar.tsx +0 -91
  53. package/src/components/MaxViewportBox.tsx +0 -19
  54. package/src/components/Menu.tsx +0 -316
  55. package/src/components/MenuOption.tsx +0 -330
  56. package/src/components/MobileDataGrid/ColumnList.tsx +0 -66
  57. package/src/components/MobileDataGrid/ColumnSelector/index.tsx +0 -97
  58. package/src/components/MobileDataGrid/GridContextProvider/GridContext.tsx +0 -25
  59. package/src/components/MobileDataGrid/GridContextProvider/index.tsx +0 -132
  60. package/src/components/MobileDataGrid/GridContextProvider/useGridContext.ts +0 -10
  61. package/src/components/MobileDataGrid/MobileDataGridCard/MobileDataGridColumn.tsx +0 -27
  62. package/src/components/MobileDataGrid/MobileDataGridCard/index.tsx +0 -138
  63. package/src/components/MobileDataGrid/MobileDataGridHeader.tsx +0 -81
  64. package/src/components/MobileDataGrid/RowDetailModalProvider/ModalContent.tsx +0 -42
  65. package/src/components/MobileDataGrid/RowDetailModalProvider/index.tsx +0 -68
  66. package/src/components/MobileDataGrid/dataGridReducer.ts +0 -55
  67. package/src/components/MobileDataGrid/index.tsx +0 -92
  68. package/src/components/MobileDataGrid/types.ts +0 -4
  69. package/src/components/Modal.tsx +0 -312
  70. package/src/components/ModalButtons.tsx +0 -62
  71. package/src/components/ModalContent.tsx +0 -31
  72. package/src/components/ModalHeader.tsx +0 -78
  73. package/src/components/ModalScrim.tsx +0 -42
  74. package/src/components/NavigationTab.tsx +0 -95
  75. package/src/components/NavigationTabs.tsx +0 -70
  76. package/src/components/NestedMenu.tsx +0 -131
  77. package/src/components/Notification.tsx +0 -128
  78. package/src/components/OptionPill.tsx +0 -139
  79. package/src/components/OrderCheckIcon.tsx +0 -19
  80. package/src/components/PDFViewer/DownloadIcon.tsx +0 -25
  81. package/src/components/PDFViewer/PDFElement.tsx +0 -90
  82. package/src/components/PDFViewer/PDFNavigation.tsx +0 -68
  83. package/src/components/PDFViewer/PDFPage.tsx +0 -34
  84. package/src/components/PDFViewer/index.tsx +0 -128
  85. package/src/components/Pagination.tsx +0 -182
  86. package/src/components/Paragraph.tsx +0 -55
  87. package/src/components/Password.tsx +0 -62
  88. package/src/components/ProductImagePreview/CarouselPagination.tsx +0 -54
  89. package/src/components/ProductImagePreview/MobileImageCarousel.tsx +0 -226
  90. package/src/components/ProductImagePreview/ProductPrimaryImage.tsx +0 -219
  91. package/src/components/ProductImagePreview/Thumbnail.tsx +0 -55
  92. package/src/components/ProductImagePreview/ZoomWindow.tsx +0 -136
  93. package/src/components/ProductImagePreview/index.tsx +0 -182
  94. package/src/components/ProductImagePreview/useProductImagePreview.ts +0 -211
  95. package/src/components/ProjectBar.tsx +0 -82
  96. package/src/components/Radio.tsx +0 -146
  97. package/src/components/Search.tsx +0 -152
  98. package/src/components/SearchResultImage/index.tsx +0 -39
  99. package/src/components/Select.tsx +0 -114
  100. package/src/components/SideMenu.tsx +0 -30
  101. package/src/components/SideMenuGroup.tsx +0 -95
  102. package/src/components/SideMenuItem.tsx +0 -109
  103. package/src/components/SimpleTable.tsx +0 -77
  104. package/src/components/SkeletonParagraph.tsx +0 -31
  105. package/src/components/Spinner.tsx +0 -32
  106. package/src/components/Stack.tsx +0 -347
  107. package/src/components/StatusPill.tsx +0 -59
  108. package/src/components/Stepper.tsx +0 -128
  109. package/src/components/Subheader.tsx +0 -50
  110. package/src/components/Surface.tsx +0 -37
  111. package/src/components/Swatch.tsx +0 -1341
  112. package/src/components/Textarea.tsx +0 -102
  113. package/src/components/Theme.tsx +0 -27
  114. package/src/components/Time.tsx +0 -460
  115. package/src/components/Toast.tsx +0 -268
  116. package/src/components/Tooltip.tsx +0 -159
  117. package/src/components/TopBar.tsx +0 -139
  118. package/src/components/Upload.tsx +0 -107
  119. package/src/components/WorldpayIframe.tsx +0 -7
  120. package/src/components/index.ts +0 -34
  121. package/src/components/useMenuSystem.tsx +0 -456
  122. package/src/components/useMounted.tsx +0 -14
  123. package/src/darkmode.css +0 -278
  124. package/src/fonts.css +0 -23
  125. package/src/hooks/index.ts +0 -4
  126. package/src/hooks/useInfiniteScroll.tsx +0 -40
  127. package/src/hooks/useKeydown.ts +0 -42
  128. package/src/hooks/useMatchesMedia.ts +0 -18
  129. package/src/hooks/useTableLayout.ts +0 -106
  130. package/src/index.css +0 -800
  131. package/src/index.tsx +0 -5
  132. package/src/types.ts +0 -150
  133. package/src/utils/date.ts +0 -236
  134. package/src/utils/formatting.tsx +0 -81
  135. package/src/utils/index.ts +0 -4
  136. package/src/utils/mergeObjectArrays.ts +0 -18
  137. package/src/utils.ts +0 -24
@@ -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
- }