@rxdrag/website-lib-core 0.0.127 → 0.0.129

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 (138) hide show
  1. package/README.md +1 -1
  2. package/index.ts +1 -1
  3. package/package.json +4 -4
  4. package/src/astro/animation.ts +146 -146
  5. package/src/astro/background.ts +82 -86
  6. package/src/astro/base.ts +7 -0
  7. package/src/astro/business.ts +13 -0
  8. package/src/astro/grid/consts.ts +80 -80
  9. package/src/astro/grid/index.ts +2 -2
  10. package/src/astro/grid/types.ts +35 -35
  11. package/src/astro/image.ts +239 -239
  12. package/src/astro/index.ts +12 -10
  13. package/src/astro/link.ts +20 -21
  14. package/src/astro/media.ts +123 -123
  15. package/src/astro/nav.ts +13 -13
  16. package/src/astro/section/index.ts +7 -12
  17. package/src/component-logic/index.ts +1 -1
  18. package/src/component-logic/link-client.ts +32 -32
  19. package/src/component-logic/link.ts +61 -61
  20. package/src/design-tokens.ts +160 -160
  21. package/src/entify/Entify.ts +111 -101
  22. package/src/entify/IEntify.ts +157 -177
  23. package/src/entify/index.ts +4 -4
  24. package/src/entify/lib/collectCategoryIds.ts +20 -20
  25. package/src/entify/lib/fulltextSearch.ts +63 -62
  26. package/src/entify/lib/langFields.ts +14 -14
  27. package/src/entify/lib/listToTree.ts +23 -23
  28. package/src/entify/lib/newAvatarQueryOptions.ts +4 -4
  29. package/src/entify/lib/newOgImageQueryOptions.ts +5 -5
  30. package/src/entify/lib/newQueryPostOptions.ts +42 -45
  31. package/src/entify/lib/newQueryProductOptions.ts +96 -98
  32. package/src/entify/lib/newQueryProductsMediaOptions.ts +28 -28
  33. package/src/entify/lib/queryAllProducts.ts +40 -40
  34. package/src/entify/lib/queryBulletin.ts +28 -16
  35. package/src/entify/lib/queryEntityList.ts +41 -41
  36. package/src/entify/lib/queryFeaturedProducts.ts +69 -68
  37. package/src/entify/lib/queryLangs.ts +36 -60
  38. package/src/entify/lib/queryLatestPosts.ts +92 -91
  39. package/src/entify/lib/queryOneEntity.ts +63 -63
  40. package/src/entify/lib/queryOneMedia.ts +27 -27
  41. package/src/entify/lib/queryOnePostById.ts +21 -21
  42. package/src/entify/lib/queryOnePostBySlug.ts +40 -39
  43. package/src/entify/lib/queryOnePostCategoryBySlug.ts +35 -35
  44. package/src/entify/lib/queryOneProductById.ts +20 -20
  45. package/src/entify/lib/queryOneProductBySlug.ts +53 -52
  46. package/src/entify/lib/queryOneProductCategoryBySlug.ts +50 -49
  47. package/src/entify/lib/queryOneTheme.ts +54 -72
  48. package/src/entify/lib/queryOneUser.ts +38 -38
  49. package/src/entify/lib/queryPostCategories.ts +67 -67
  50. package/src/entify/lib/queryPostSlugs.ts +37 -37
  51. package/src/entify/lib/queryPosts.ts +175 -174
  52. package/src/entify/lib/queryProductCategories.ts +59 -59
  53. package/src/entify/lib/queryProducts.ts +145 -144
  54. package/src/entify/lib/queryProductsInMenu.ts +45 -45
  55. package/src/entify/lib/queryTagCategories.ts +58 -58
  56. package/src/entify/lib/queryTags.ts +57 -57
  57. package/src/entify/lib/queryUserIds.ts +24 -24
  58. package/src/entify/lib/queryUserPosts.ts +80 -79
  59. package/src/entify/lib/queryWebSiteSettings.ts +28 -28
  60. package/src/entify/lib/queryWebsite.ts +43 -43
  61. package/src/entify/lib/sendEmail.ts +7 -7
  62. package/src/entify/lib/toQueryOptions.ts +19 -19
  63. package/src/entify/lib/upsertEntity.ts +8 -8
  64. package/src/entify/types/index.ts +1 -1
  65. package/src/entify/types/utils.ts +11 -6
  66. package/src/entify/types/variables.ts +0 -1
  67. package/src/entify/view-model/funcs.ts +230 -230
  68. package/src/entify/view-model/index.ts +1 -1
  69. package/src/entify/view-model/models.ts +135 -135
  70. package/src/global.d.ts +7 -7
  71. package/src/index.ts +8 -8
  72. package/src/lib/formatDate.ts +15 -15
  73. package/src/lib/index.ts +3 -3
  74. package/src/lib/pagination.ts +114 -114
  75. package/src/lib/utils.ts +135 -135
  76. package/src/react/components/Analytics/eventHandlers.ts +173 -173
  77. package/src/react/components/Analytics/index.tsx +21 -21
  78. package/src/react/components/Analytics/singleton.ts +214 -214
  79. package/src/react/components/Analytics/tracking.ts +221 -221
  80. package/src/react/components/Analytics/types.ts +60 -60
  81. package/src/react/components/Analytics/utils.ts +95 -95
  82. package/src/react/components/AttachmentIcon/index.tsx +53 -53
  83. package/src/react/components/BackgroundHlsVideoPlayer.tsx +97 -97
  84. package/src/react/components/BackgroundVideoPlayer.tsx +32 -32
  85. package/src/react/components/Bulletin.tsx +30 -30
  86. package/src/react/components/ContactForm/ContactForm.tsx +289 -289
  87. package/src/react/components/ContactForm/Input.tsx +48 -48
  88. package/src/react/components/ContactForm/Input2.tsx +59 -59
  89. package/src/react/components/ContactForm/Submit.tsx +48 -48
  90. package/src/react/components/ContactForm/TelInput.tsx +215 -215
  91. package/src/react/components/ContactForm/Textarea.tsx +48 -48
  92. package/src/react/components/ContactForm/Textarea2.tsx +89 -89
  93. package/src/react/components/ContactForm/funcs.ts +64 -64
  94. package/src/react/components/ContactForm/index.ts +7 -7
  95. package/src/react/components/ContactForm/types.ts +68 -68
  96. package/src/react/components/GoogleConsent/CookieItemPanel.tsx +80 -80
  97. package/src/react/components/GoogleConsent/CumtomizedModal.tsx +148 -148
  98. package/src/react/components/GoogleConsent/GoogleConsent.tsx +100 -100
  99. package/src/react/components/GoogleConsent/gtags.ts +67 -67
  100. package/src/react/components/GoogleConsent/index.ts +2 -2
  101. package/src/react/components/GoogleConsent/types.ts +18 -18
  102. package/src/react/components/GoogleConsent//345/217/202/350/200/203.md +4 -4
  103. package/src/react/components/Icon/index.tsx +19 -19
  104. package/src/react/components/Medias/MainMedia.tsx +257 -257
  105. package/src/react/components/Medias/Thumbnail.tsx +62 -62
  106. package/src/react/components/Medias/VideoPlayer.tsx +114 -114
  107. package/src/react/components/Medias/index.tsx +271 -271
  108. package/src/react/components/ProductCard/ProductCard.tsx +24 -24
  109. package/src/react/components/ProductCard/ProductCta/index.tsx +28 -28
  110. package/src/react/components/ProductCard/ProductCta/style.css +3 -3
  111. package/src/react/components/ProductCard/ProductDescription/index.tsx +12 -12
  112. package/src/react/components/ProductCard/ProductDescription/style.css +5 -5
  113. package/src/react/components/ProductCard/ProductMedia/index.tsx +35 -35
  114. package/src/react/components/ProductCard/ProductMedia/style.css +5 -5
  115. package/src/react/components/ProductCard/ProductTitle/index.tsx +7 -7
  116. package/src/react/components/ProductCard/ProductTitle/style.css +3 -3
  117. package/src/react/components/ProductCard/ProductView.tsx +36 -36
  118. package/src/react/components/ProductCard/index.ts +4 -4
  119. package/src/react/components/ProductCard/useQueryProduct.ts +32 -32
  120. package/src/react/components/ReactModalTrigger.tsx +28 -28
  121. package/src/react/components/ReactVideoPlayer.tsx +29 -52
  122. package/src/react/components/RichTextOutline/index.tsx +75 -75
  123. package/src/react/components/RichTextOutline/useAnchorScroll.ts +23 -23
  124. package/src/react/components/Scroller.tsx +39 -39
  125. package/src/react/components/SearchInput.tsx +21 -21
  126. package/src/react/components/Share/index.tsx +86 -86
  127. package/src/react/components/Share/socials.tsx +79 -77
  128. package/src/react/components/Share//350/265/204/346/226/231.md +7 -7
  129. package/src/react/components/ToTop.tsx +72 -72
  130. package/src/react/components/VideoPlayIcon.tsx +43 -0
  131. package/src/react/components/all.ts +38 -38
  132. package/src/react/components/index.ts +16 -16
  133. package/src/robots.ts +4 -4
  134. package/src/entify/lib/newPageMetaOptions.ts +0 -18
  135. package/src/entify/lib/newQueryPageOptions.ts +0 -14
  136. package/src/entify/lib/queryOneIcon.ts +0 -27
  137. package/src/entify/lib/queryPageBySlug.ts +0 -43
  138. package/src/entify/lib/queryPageByType.ts +0 -44
@@ -1,257 +1,257 @@
1
- import clsx from "clsx";
2
- import { useRef, useState, useEffect, useCallback } from "react";
3
- import { TMedia } from "../../../entify";
4
- import { MediaType } from "@rxdrag/rxcms-models";
5
- import { VideoPlayer } from "./VideoPlayer";
6
-
7
- export type MainMediaProps = {
8
- value?: TMedia[];
9
- selectedId?: string | null;
10
- aspect?: string;
11
- enableZoom?: boolean;
12
- className?: string;
13
- arrowButtonClass?: string;
14
- arrowIconClass?: string;
15
- playButtonClass?: string;
16
- onPrevious: () => void;
17
- onNext: () => void;
18
- canPrevious: boolean;
19
- canNext: boolean;
20
- };
21
-
22
- export const MainMedia = ({
23
- value,
24
- selectedId,
25
- aspect,
26
- enableZoom,
27
- className,
28
- arrowButtonClass,
29
- arrowIconClass,
30
- playButtonClass,
31
- onPrevious,
32
- onNext,
33
- canPrevious,
34
- canNext,
35
- }: MainMediaProps) => {
36
- const mainAreaRef = useRef<HTMLDivElement>(null);
37
- const [isZoomed, setIsZoomed] = useState(false);
38
- const [position, setPosition] = useState({ x: 0, y: 0 });
39
- const hoverTimerRef = useRef<NodeJS.Timeout | null>(null);
40
- const lastMousePosRef = useRef({ x: 0, y: 0 });
41
-
42
- const selectedMedia = value?.find((media) => media.id === selectedId);
43
- const isVideo = selectedMedia?.mediaType === MediaType.video;
44
-
45
- // Reset zoom state when image changes
46
- useEffect(() => {
47
- setIsZoomed(false);
48
- setPosition({ x: 0, y: 0 });
49
- if (hoverTimerRef.current) {
50
- clearTimeout(hoverTimerRef.current);
51
- }
52
- }, [selectedId]);
53
-
54
- // Clean up timer on unmount
55
- useEffect(() => {
56
- return () => {
57
- if (hoverTimerRef.current) {
58
- clearTimeout(hoverTimerRef.current);
59
- }
60
- };
61
- }, []);
62
-
63
- const updateZoomPosition = useCallback((clientX: number, clientY: number) => {
64
- if (mainAreaRef.current) {
65
- const { left, top, width, height } =
66
- mainAreaRef.current.getBoundingClientRect();
67
- const x = clientX - left;
68
- const y = clientY - top;
69
-
70
- // Calculate percentage position (0 to 1)
71
- const ratioX = Math.max(0, Math.min(1, x / width));
72
- const ratioY = Math.max(0, Math.min(1, y / height));
73
-
74
- // Formula: Translate = Dimension * (0.5 - Ratio)
75
- // This shifts the image opposite to mouse movement relative to center
76
- const newX = width * (0.5 - ratioX);
77
- const newY = height * (0.5 - ratioY);
78
-
79
- setPosition({ x: newX, y: newY });
80
- }
81
- }, []);
82
-
83
- const handleMouseEnter = useCallback(
84
- (e: React.MouseEvent) => {
85
- if (!enableZoom || isVideo) return;
86
- lastMousePosRef.current = { x: e.clientX, y: e.clientY };
87
-
88
- hoverTimerRef.current = setTimeout(() => {
89
- setIsZoomed(true);
90
- updateZoomPosition(
91
- lastMousePosRef.current.x,
92
- lastMousePosRef.current.y
93
- );
94
- }, 1000);
95
- },
96
- [enableZoom, isVideo, updateZoomPosition]
97
- );
98
-
99
- const handleMouseMove = useCallback(
100
- (e: React.MouseEvent) => {
101
- if (!enableZoom || isVideo) return;
102
- lastMousePosRef.current = { x: e.clientX, y: e.clientY };
103
-
104
- if (isZoomed) {
105
- updateZoomPosition(e.clientX, e.clientY);
106
- }
107
- },
108
- [enableZoom, isVideo, isZoomed, updateZoomPosition]
109
- );
110
-
111
- const handleMouseLeave = useCallback(() => {
112
- if (hoverTimerRef.current) {
113
- clearTimeout(hoverTimerRef.current);
114
- }
115
- setIsZoomed(false);
116
- setPosition({ x: 0, y: 0 });
117
- }, []);
118
-
119
- const handleClick = useCallback(
120
- (e: React.MouseEvent) => {
121
- if (!enableZoom || isVideo) return;
122
-
123
- if (hoverTimerRef.current) {
124
- clearTimeout(hoverTimerRef.current);
125
- }
126
-
127
- if (isZoomed) {
128
- setIsZoomed(false);
129
- setPosition({ x: 0, y: 0 });
130
- } else {
131
- setIsZoomed(true);
132
- updateZoomPosition(e.clientX, e.clientY);
133
- }
134
- },
135
- [enableZoom, isVideo, isZoomed, updateZoomPosition]
136
- );
137
-
138
- return (
139
- <div
140
- ref={mainAreaRef}
141
- className={clsx(
142
- "relative group overflow-hidden rounded-xl bg-gray-100 dark:bg-gray-800 z-0",
143
- aspect,
144
- className,
145
- enableZoom && !isVideo && "cursor-crosshair"
146
- )}
147
- onMouseEnter={handleMouseEnter}
148
- onMouseMove={handleMouseMove}
149
- onMouseLeave={handleMouseLeave}
150
- onClick={handleClick}
151
- >
152
- {/* Navigation Arrows - Hide when zoomed to avoid misclick */}
153
- {!isZoomed && (
154
- <div className="absolute inset-0 flex items-center justify-between p-4 pointer-events-none z-20">
155
- <button
156
- onClick={(e) => {
157
- e.stopPropagation();
158
- onPrevious();
159
- }}
160
- disabled={!canPrevious}
161
- className={clsx(
162
- "pointer-events-auto transition-all duration-200 rounded-full p-2",
163
- "bg-white/80 dark:bg-black/50 backdrop-blur-sm shadow-sm",
164
- "hover:bg-white dark:hover:bg-black/70 hover:scale-105 active:scale-95",
165
- "text-gray-800 dark:text-white",
166
- !canPrevious
167
- ? "opacity-0 translate-x-[-10px]"
168
- : "opacity-0 group-hover:opacity-100 translate-x-0",
169
- arrowButtonClass
170
- )}
171
- aria-label="Previous slide"
172
- >
173
- <svg
174
- xmlns="http://www.w3.org/2000/svg"
175
- className={clsx("h-5 w-5", arrowIconClass)}
176
- fill="none"
177
- viewBox="0 0 24 24"
178
- stroke="currentColor"
179
- >
180
- <path
181
- strokeLinecap="round"
182
- strokeLinejoin="round"
183
- strokeWidth={2}
184
- d="M15 19l-7-7 7-7"
185
- />
186
- </svg>
187
- </button>
188
-
189
- <button
190
- onClick={(e) => {
191
- e.stopPropagation();
192
- onNext();
193
- }}
194
- disabled={!canNext}
195
- className={clsx(
196
- "pointer-events-auto transition-all duration-200 rounded-full p-2",
197
- "bg-white/80 dark:bg-black/50 backdrop-blur-sm shadow-sm",
198
- "hover:bg-white dark:hover:bg-black/70 hover:scale-105 active:scale-95",
199
- "text-gray-800 dark:text-white",
200
- !canNext
201
- ? "opacity-0 translate-x-[10px]"
202
- : "opacity-0 group-hover:opacity-100 translate-x-0",
203
- arrowButtonClass
204
- )}
205
- aria-label="Next slide"
206
- >
207
- <svg
208
- xmlns="http://www.w3.org/2000/svg"
209
- className={clsx("h-5 w-5", arrowIconClass)}
210
- fill="none"
211
- viewBox="0 0 24 24"
212
- stroke="currentColor"
213
- >
214
- <path
215
- strokeLinecap="round"
216
- strokeLinejoin="round"
217
- strokeWidth={2}
218
- d="M9 5l7 7-7 7"
219
- />
220
- </svg>
221
- </button>
222
- </div>
223
- )}
224
-
225
- {value?.map((media) => (
226
- <div
227
- key={media.id}
228
- className={clsx(
229
- "absolute inset-0 transition-opacity duration-500 ease-in-out",
230
- media.id === selectedId
231
- ? "opacity-100 z-10"
232
- : "opacity-0 z-0 pointer-events-none"
233
- )}
234
- >
235
- {media.mediaType === MediaType.video ? (
236
- <VideoPlayer media={media} playButtonClass={playButtonClass} />
237
- ) : (
238
- <img
239
- src={media?.file?.resize || media?.file?.url}
240
- alt={media?.alt}
241
- className="w-full h-full object-cover object-center origin-center"
242
- style={{
243
- transform:
244
- media.id === selectedId
245
- ? `translate(${position.x}px, ${position.y}px) scale(${
246
- isZoomed ? 2 : 1
247
- })`
248
- : undefined,
249
- transition: isZoomed ? "none" : "transform 0.3s ease-out",
250
- }}
251
- />
252
- )}
253
- </div>
254
- ))}
255
- </div>
256
- );
257
- };
1
+ import clsx from "clsx";
2
+ import { useRef, useState, useEffect, useCallback } from "react";
3
+ import { TMedia } from "../../../entify";
4
+ import { MediaType } from "@rxdrag/rxcms-models";
5
+ import { VideoPlayer } from "./VideoPlayer";
6
+
7
+ export type MainMediaProps = {
8
+ value?: TMedia[];
9
+ selectedId?: string | null;
10
+ aspect?: string;
11
+ enableZoom?: boolean;
12
+ className?: string;
13
+ arrowButtonClass?: string;
14
+ arrowIconClass?: string;
15
+ playButtonClass?: string;
16
+ onPrevious: () => void;
17
+ onNext: () => void;
18
+ canPrevious: boolean;
19
+ canNext: boolean;
20
+ };
21
+
22
+ export const MainMedia = ({
23
+ value,
24
+ selectedId,
25
+ aspect,
26
+ enableZoom,
27
+ className,
28
+ arrowButtonClass,
29
+ arrowIconClass,
30
+ playButtonClass,
31
+ onPrevious,
32
+ onNext,
33
+ canPrevious,
34
+ canNext,
35
+ }: MainMediaProps) => {
36
+ const mainAreaRef = useRef<HTMLDivElement>(null);
37
+ const [isZoomed, setIsZoomed] = useState(false);
38
+ const [position, setPosition] = useState({ x: 0, y: 0 });
39
+ const hoverTimerRef = useRef<NodeJS.Timeout | null>(null);
40
+ const lastMousePosRef = useRef({ x: 0, y: 0 });
41
+
42
+ const selectedMedia = value?.find((media) => media.id === selectedId);
43
+ const isVideo = selectedMedia?.mediaType === MediaType.video;
44
+
45
+ // Reset zoom state when image changes
46
+ useEffect(() => {
47
+ setIsZoomed(false);
48
+ setPosition({ x: 0, y: 0 });
49
+ if (hoverTimerRef.current) {
50
+ clearTimeout(hoverTimerRef.current);
51
+ }
52
+ }, [selectedId]);
53
+
54
+ // Clean up timer on unmount
55
+ useEffect(() => {
56
+ return () => {
57
+ if (hoverTimerRef.current) {
58
+ clearTimeout(hoverTimerRef.current);
59
+ }
60
+ };
61
+ }, []);
62
+
63
+ const updateZoomPosition = useCallback((clientX: number, clientY: number) => {
64
+ if (mainAreaRef.current) {
65
+ const { left, top, width, height } =
66
+ mainAreaRef.current.getBoundingClientRect();
67
+ const x = clientX - left;
68
+ const y = clientY - top;
69
+
70
+ // Calculate percentage position (0 to 1)
71
+ const ratioX = Math.max(0, Math.min(1, x / width));
72
+ const ratioY = Math.max(0, Math.min(1, y / height));
73
+
74
+ // Formula: Translate = Dimension * (0.5 - Ratio)
75
+ // This shifts the image opposite to mouse movement relative to center
76
+ const newX = width * (0.5 - ratioX);
77
+ const newY = height * (0.5 - ratioY);
78
+
79
+ setPosition({ x: newX, y: newY });
80
+ }
81
+ }, []);
82
+
83
+ const handleMouseEnter = useCallback(
84
+ (e: React.MouseEvent) => {
85
+ if (!enableZoom || isVideo) return;
86
+ lastMousePosRef.current = { x: e.clientX, y: e.clientY };
87
+
88
+ hoverTimerRef.current = setTimeout(() => {
89
+ setIsZoomed(true);
90
+ updateZoomPosition(
91
+ lastMousePosRef.current.x,
92
+ lastMousePosRef.current.y
93
+ );
94
+ }, 1000);
95
+ },
96
+ [enableZoom, isVideo, updateZoomPosition]
97
+ );
98
+
99
+ const handleMouseMove = useCallback(
100
+ (e: React.MouseEvent) => {
101
+ if (!enableZoom || isVideo) return;
102
+ lastMousePosRef.current = { x: e.clientX, y: e.clientY };
103
+
104
+ if (isZoomed) {
105
+ updateZoomPosition(e.clientX, e.clientY);
106
+ }
107
+ },
108
+ [enableZoom, isVideo, isZoomed, updateZoomPosition]
109
+ );
110
+
111
+ const handleMouseLeave = useCallback(() => {
112
+ if (hoverTimerRef.current) {
113
+ clearTimeout(hoverTimerRef.current);
114
+ }
115
+ setIsZoomed(false);
116
+ setPosition({ x: 0, y: 0 });
117
+ }, []);
118
+
119
+ const handleClick = useCallback(
120
+ (e: React.MouseEvent) => {
121
+ if (!enableZoom || isVideo) return;
122
+
123
+ if (hoverTimerRef.current) {
124
+ clearTimeout(hoverTimerRef.current);
125
+ }
126
+
127
+ if (isZoomed) {
128
+ setIsZoomed(false);
129
+ setPosition({ x: 0, y: 0 });
130
+ } else {
131
+ setIsZoomed(true);
132
+ updateZoomPosition(e.clientX, e.clientY);
133
+ }
134
+ },
135
+ [enableZoom, isVideo, isZoomed, updateZoomPosition]
136
+ );
137
+
138
+ return (
139
+ <div
140
+ ref={mainAreaRef}
141
+ className={clsx(
142
+ "relative group overflow-hidden rounded-xl bg-gray-100 dark:bg-gray-800 z-0",
143
+ aspect,
144
+ className,
145
+ enableZoom && !isVideo && "cursor-crosshair"
146
+ )}
147
+ onMouseEnter={handleMouseEnter}
148
+ onMouseMove={handleMouseMove}
149
+ onMouseLeave={handleMouseLeave}
150
+ onClick={handleClick}
151
+ >
152
+ {/* Navigation Arrows - Hide when zoomed to avoid misclick */}
153
+ {!isZoomed && (
154
+ <div className="absolute inset-0 flex items-center justify-between p-4 pointer-events-none z-20">
155
+ <button
156
+ onClick={(e) => {
157
+ e.stopPropagation();
158
+ onPrevious();
159
+ }}
160
+ disabled={!canPrevious}
161
+ className={clsx(
162
+ "pointer-events-auto transition-all duration-200 rounded-full p-2",
163
+ "bg-white/80 dark:bg-black/50 backdrop-blur-sm shadow-sm",
164
+ "hover:bg-white dark:hover:bg-black/70 hover:scale-105 active:scale-95",
165
+ "text-gray-800 dark:text-white",
166
+ !canPrevious
167
+ ? "opacity-0 translate-x-[-10px]"
168
+ : "opacity-0 group-hover:opacity-100 translate-x-0",
169
+ arrowButtonClass
170
+ )}
171
+ aria-label="Previous slide"
172
+ >
173
+ <svg
174
+ xmlns="http://www.w3.org/2000/svg"
175
+ className={clsx("h-5 w-5", arrowIconClass)}
176
+ fill="none"
177
+ viewBox="0 0 24 24"
178
+ stroke="currentColor"
179
+ >
180
+ <path
181
+ strokeLinecap="round"
182
+ strokeLinejoin="round"
183
+ strokeWidth={2}
184
+ d="M15 19l-7-7 7-7"
185
+ />
186
+ </svg>
187
+ </button>
188
+
189
+ <button
190
+ onClick={(e) => {
191
+ e.stopPropagation();
192
+ onNext();
193
+ }}
194
+ disabled={!canNext}
195
+ className={clsx(
196
+ "pointer-events-auto transition-all duration-200 rounded-full p-2",
197
+ "bg-white/80 dark:bg-black/50 backdrop-blur-sm shadow-sm",
198
+ "hover:bg-white dark:hover:bg-black/70 hover:scale-105 active:scale-95",
199
+ "text-gray-800 dark:text-white",
200
+ !canNext
201
+ ? "opacity-0 translate-x-[10px]"
202
+ : "opacity-0 group-hover:opacity-100 translate-x-0",
203
+ arrowButtonClass
204
+ )}
205
+ aria-label="Next slide"
206
+ >
207
+ <svg
208
+ xmlns="http://www.w3.org/2000/svg"
209
+ className={clsx("h-5 w-5", arrowIconClass)}
210
+ fill="none"
211
+ viewBox="0 0 24 24"
212
+ stroke="currentColor"
213
+ >
214
+ <path
215
+ strokeLinecap="round"
216
+ strokeLinejoin="round"
217
+ strokeWidth={2}
218
+ d="M9 5l7 7-7 7"
219
+ />
220
+ </svg>
221
+ </button>
222
+ </div>
223
+ )}
224
+
225
+ {value?.map((media) => (
226
+ <div
227
+ key={media.id}
228
+ className={clsx(
229
+ "absolute inset-0 transition-opacity duration-500 ease-in-out",
230
+ media.id === selectedId
231
+ ? "opacity-100 z-10"
232
+ : "opacity-0 z-0 pointer-events-none"
233
+ )}
234
+ >
235
+ {media.mediaType === MediaType.video ? (
236
+ <VideoPlayer media={media} playButtonClass={playButtonClass} />
237
+ ) : (
238
+ <img
239
+ src={media?.file?.resize || media?.file?.url}
240
+ alt={media?.alt}
241
+ className="w-full h-full object-cover object-center origin-center"
242
+ style={{
243
+ transform:
244
+ media.id === selectedId
245
+ ? `translate(${position.x}px, ${position.y}px) scale(${
246
+ isZoomed ? 2 : 1
247
+ })`
248
+ : undefined,
249
+ transition: isZoomed ? "none" : "transform 0.3s ease-out",
250
+ }}
251
+ />
252
+ )}
253
+ </div>
254
+ ))}
255
+ </div>
256
+ );
257
+ };
@@ -1,62 +1,62 @@
1
- import clsx from "clsx";
2
- import { TMedia } from "../../../entify";
3
- import { MediaType } from "@rxdrag/rxcms-models";
4
- import { VideoPlayer } from "./VideoPlayer";
5
-
6
- export type ThumbnailProps = {
7
- media: TMedia;
8
- isSelected: boolean;
9
- onClick: () => void;
10
- aspect?: string;
11
- className?: string;
12
- imageClass?: string;
13
- playButtonClass?: string;
14
- };
15
-
16
- export const Thumbnail = ({
17
- media,
18
- isSelected,
19
- onClick,
20
- aspect,
21
- className,
22
- imageClass,
23
- playButtonClass,
24
- }: ThumbnailProps) => {
25
- return (
26
- <div
27
- className={clsx(
28
- "relative cursor-pointer overflow-hidden rounded-lg transition-all duration-200",
29
- aspect,
30
- isSelected
31
- ? "ring-2 ring-primary ring-offset-2 ring-offset-white dark:ring-offset-gray-950"
32
- : "opacity-70 hover:opacity-100 hover:ring-2 hover:ring-gray-200 dark:hover:ring-gray-700",
33
- className
34
- )}
35
- onClick={onClick}
36
- >
37
- {media.mediaType === MediaType.video ? (
38
- <div
39
- className={clsx(
40
- "w-full h-full relative pointer-events-none [&>div]:!aspect-auto [&>div]:!w-full [&>div]:!h-full transition-transform duration-500",
41
- isSelected ? "scale-110" : "scale-100",
42
- imageClass
43
- )}
44
- >
45
- <VideoPlayer media={media} size="small" playButtonClass={playButtonClass} />
46
- </div>
47
- ) : (
48
- <img
49
- src={
50
- media?.file?.thumbnail || media?.file?.resize || media?.file?.url
51
- }
52
- alt={media?.alt}
53
- className={clsx(
54
- "w-full h-full object-cover transition-transform duration-500",
55
- isSelected ? "scale-110" : "scale-100",
56
- imageClass
57
- )}
58
- />
59
- )}
60
- </div>
61
- );
62
- };
1
+ import clsx from "clsx";
2
+ import { TMedia } from "../../../entify";
3
+ import { MediaType } from "@rxdrag/rxcms-models";
4
+ import { VideoPlayer } from "./VideoPlayer";
5
+
6
+ export type ThumbnailProps = {
7
+ media: TMedia;
8
+ isSelected: boolean;
9
+ onClick: () => void;
10
+ aspect?: string;
11
+ className?: string;
12
+ imageClass?: string;
13
+ playButtonClass?: string;
14
+ };
15
+
16
+ export const Thumbnail = ({
17
+ media,
18
+ isSelected,
19
+ onClick,
20
+ aspect,
21
+ className,
22
+ imageClass,
23
+ playButtonClass,
24
+ }: ThumbnailProps) => {
25
+ return (
26
+ <div
27
+ className={clsx(
28
+ "relative cursor-pointer overflow-hidden rounded-lg transition-all duration-200",
29
+ aspect,
30
+ isSelected
31
+ ? "ring-2 ring-primary ring-offset-2 ring-offset-white dark:ring-offset-gray-950"
32
+ : "opacity-70 hover:opacity-100 hover:ring-2 hover:ring-gray-200 dark:hover:ring-gray-700",
33
+ className
34
+ )}
35
+ onClick={onClick}
36
+ >
37
+ {media.mediaType === MediaType.video ? (
38
+ <div
39
+ className={clsx(
40
+ "w-full h-full relative pointer-events-none [&>div]:!aspect-auto [&>div]:!w-full [&>div]:!h-full transition-transform duration-500",
41
+ isSelected ? "scale-110" : "scale-100",
42
+ imageClass
43
+ )}
44
+ >
45
+ <VideoPlayer media={media} size="small" playButtonClass={playButtonClass} />
46
+ </div>
47
+ ) : (
48
+ <img
49
+ src={
50
+ media?.file?.thumbnail || media?.file?.resize || media?.file?.url
51
+ }
52
+ alt={media?.alt}
53
+ className={clsx(
54
+ "w-full h-full object-cover transition-transform duration-500",
55
+ isSelected ? "scale-110" : "scale-100",
56
+ imageClass
57
+ )}
58
+ />
59
+ )}
60
+ </div>
61
+ );
62
+ };