@rxdrag/website-lib-core 0.0.7 → 0.0.8

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 (71) hide show
  1. package/index.ts +1 -1
  2. package/package.json +10 -6
  3. package/src/component-logic/gsap.d.ts +4 -0
  4. package/src/component-logic/index.ts +8 -0
  5. package/src/component-logic/link-client.ts +33 -0
  6. package/src/component-logic/link.ts +50 -0
  7. package/src/component-logic/modal.ts +36 -0
  8. package/src/component-logic/motion.ts +272 -0
  9. package/src/component-logic/number.ts +45 -0
  10. package/src/component-logic/popover.ts +51 -0
  11. package/src/component-logic/tabs.ts +10 -0
  12. package/src/controller/AnimateController.ts +138 -0
  13. package/src/controller/AosController.ts +240 -0
  14. package/src/controller/FlipController.ts +339 -0
  15. package/src/controller/ModalController.ts +127 -0
  16. package/src/controller/NumberController.ts +161 -0
  17. package/src/controller/PageLoader.ts +163 -0
  18. package/src/controller/PopoverController.ts +116 -0
  19. package/src/controller/TabsController.ts +271 -0
  20. package/src/controller/applyAnimation.ts +86 -0
  21. package/src/controller/applyInitialState.ts +79 -0
  22. package/src/{scripts → controller}/consts.ts +0 -2
  23. package/src/controller/index.ts +9 -0
  24. package/src/controller/popup.ts +346 -0
  25. package/src/controller/utils.ts +48 -0
  26. package/src/entify/Entify.ts +354 -365
  27. package/src/entify/IEntify.ts +91 -0
  28. package/src/entify/index.ts +3 -2
  29. package/src/entify/lib/newQueryProductOptions.ts +2 -3
  30. package/src/entify/lib/newQueryProductsMediaOptions.ts +19 -18
  31. package/src/entify/lib/queryAllProducts.ts +11 -3
  32. package/src/entify/lib/queryFeaturedProducts.ts +3 -3
  33. package/src/entify/lib/queryLatestPosts.ts +2 -2
  34. package/src/entify/lib/queryOneTheme.ts +1 -1
  35. package/src/entify/lib/queryPostCategories.ts +3 -3
  36. package/src/entify/lib/queryPostSlugs.ts +2 -2
  37. package/src/entify/lib/queryPosts.ts +92 -92
  38. package/src/entify/lib/queryProductCategories.ts +3 -3
  39. package/src/entify/lib/queryProducts.ts +69 -69
  40. package/src/entify/lib/queryUserPosts.ts +2 -2
  41. package/src/entify/lib/searchProducts.ts +2 -2
  42. package/src/index.ts +3 -1
  43. package/src/lib/formatDate.ts +15 -0
  44. package/src/lib/index.ts +3 -0
  45. package/src/lib/pagination.ts +114 -0
  46. package/src/lib/utils.ts +119 -0
  47. package/src/motion/consts.ts +428 -598
  48. package/src/motion/convertToGsapVars.ts +102 -0
  49. package/src/motion/index.ts +5 -1
  50. package/src/motion/normalizeAnimation.ts +28 -0
  51. package/src/motion/normalizeAosAnimation.ts +22 -0
  52. package/src/motion/normalizePopupAnimation.ts +24 -0
  53. package/src/motion/types.ts +133 -46
  54. package/src/react/components/AttachmentIcon/index.tsx +53 -0
  55. package/src/react/components/ContactForm/index.tsx +341 -0
  56. package/src/react/components/Icon/index.tsx +10 -0
  57. package/src/react/components/Medias/index.tsx +347 -347
  58. package/src/react/components/ProductCard/ProductCta/index.tsx +7 -5
  59. package/src/react/components/RichTextOutline/index.tsx +76 -76
  60. package/src/react/components/Scroller.tsx +5 -1
  61. package/src/react/components/SearchInput.tsx +36 -34
  62. package/src/react/components/ToTop.tsx +63 -28
  63. package/src/react/components/index.ts +3 -1
  64. package/src/react/hooks/useScroll.ts +16 -10
  65. package/src/react/components/EnquiryForm/index.tsx +0 -334
  66. package/src/scripts/actions.ts +0 -304
  67. package/src/scripts/events.ts +0 -33
  68. package/src/scripts/index.ts +0 -3
  69. /package/src/react/components/{EnquiryForm → ContactForm}/Input.tsx +0 -0
  70. /package/src/react/components/{EnquiryForm → ContactForm}/Submit.tsx +0 -0
  71. /package/src/react/components/{EnquiryForm → ContactForm}/Textarea.tsx +0 -0
@@ -1,347 +1,347 @@
1
- import clsx from "clsx";
2
- import { forwardRef, useEffect, useState, useCallback } from "react";
3
- import { TMedias } from "../../../entify";
4
-
5
- export type MediasProps = {
6
- value?: TMedias;
7
- className?: string;
8
- children?: React.ReactNode;
9
- // Aspect ratio, format is `aspect-[width/height]`
10
- aspect?: string;
11
- };
12
-
13
- export const Medias = forwardRef<HTMLDivElement, MediasProps>(
14
- (props, ref) => {
15
- const {
16
- value,
17
- className,
18
- children,
19
- aspect = "aspect-[5/4]",
20
- ...rest
21
- } = props;
22
- const [selectedId, setSelectedId] = useState<string | undefined | null>(
23
- value?.externalVideoUrl ? "video" : value?.medias?.[0]?.id || ""
24
- );
25
- const [videoUrl, setVideoUrl] = useState<string>("");
26
- const [thumbnailUrl, setThumbnailUrl] = useState<string>("");
27
-
28
- useEffect(() => {
29
- setSelectedId(
30
- value?.externalVideoUrl ? "video" : value?.medias?.[0]?.id || ""
31
- );
32
- }, [value]);
33
-
34
- useEffect(() => {
35
- if (value?.externalVideoUrl) {
36
- const baseUrl = value.externalVideoUrl.replace(
37
- "https://youtu.be/",
38
- "https://www.youtube.com/embed/"
39
- );
40
- const separator = baseUrl.includes("?") ? "&" : "?";
41
- setVideoUrl(
42
- `${baseUrl}${separator}autoplay=1&muted=1&modestbranding=1&rel=0&controls=1&playsinline=1&enablejsapi=1&origin=${encodeURIComponent(
43
- window.location.origin
44
- )}`
45
- );
46
- }
47
- }, [value?.externalVideoUrl]);
48
-
49
- useEffect(() => {
50
- if (value?.externalVideoUrl) {
51
- const videoId = value.externalVideoUrl.includes("youtu.be/")
52
- ? value.externalVideoUrl.split("youtu.be/")[1].split("?")[0]
53
- : value.externalVideoUrl.split("v=")[1]?.split("&")[0];
54
-
55
- if (videoId) {
56
- setThumbnailUrl(
57
- `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`
58
- );
59
- }
60
- }
61
- }, [value?.externalVideoUrl]);
62
-
63
- const selectedIndex = value?.externalVideoUrl
64
- ? selectedId === "video"
65
- ? 0
66
- : (value?.medias?.findIndex((media) => media.id === selectedId) || 0) +
67
- 1
68
- : value?.medias?.findIndex((media) => media.id === selectedId) || 0;
69
-
70
- const totalItems =
71
- (value?.externalVideoUrl ? 1 : 0) + (value?.medias?.length || 0);
72
-
73
- // Calculate visible thumbnails (show 6 items)
74
- const visibleCount = 6;
75
- const halfVisible = Math.floor(visibleCount / 2);
76
- const startIndex = Math.max(
77
- 0,
78
- Math.min(selectedIndex - halfVisible, totalItems - visibleCount)
79
- );
80
- const endIndex = Math.min(startIndex + visibleCount, totalItems);
81
-
82
- const handlePrevious = useCallback(() => {
83
- if (selectedIndex > 0) {
84
- if (value?.externalVideoUrl) {
85
- if (selectedIndex === 1) {
86
- setSelectedId("video");
87
- } else {
88
- const prevIndex = selectedIndex - 2;
89
- setSelectedId(value.medias?.[prevIndex]?.id || "");
90
- }
91
- } else {
92
- const prevIndex = selectedIndex - 1;
93
- setSelectedId(value?.medias?.[prevIndex]?.id || "");
94
- }
95
- }
96
- }, [selectedIndex, value]);
97
-
98
- const handleNext = useCallback(() => {
99
- if (selectedIndex < totalItems - 1) {
100
- if (value?.externalVideoUrl) {
101
- if (selectedId === "video") {
102
- setSelectedId(value.medias?.[0]?.id || "");
103
- } else {
104
- const currentMediaIndex =
105
- value.medias?.findIndex((media) => media.id === selectedId) || 0;
106
- const nextIndex = currentMediaIndex + 1;
107
- setSelectedId(value.medias?.[nextIndex]?.id || "");
108
- }
109
- } else {
110
- const currentMediaIndex =
111
- value?.medias?.findIndex((media) => media.id === selectedId) || 0;
112
- const nextIndex = currentMediaIndex + 1;
113
- setSelectedId(value?.medias?.[nextIndex]?.id || "");
114
- }
115
- }
116
- }, [selectedIndex, totalItems, value, selectedId]);
117
-
118
- const handleKeyDown = useCallback(
119
- (e: KeyboardEvent) => {
120
- if (e.key === "ArrowLeft") {
121
- handlePrevious();
122
- } else if (e.key === "ArrowRight") {
123
- handleNext();
124
- }
125
- },
126
- [handleNext, handlePrevious]
127
- );
128
-
129
- useEffect(() => {
130
- window.addEventListener("keydown", handleKeyDown);
131
- return () => window.removeEventListener("keydown", handleKeyDown);
132
- }, [handleKeyDown]);
133
-
134
- const canPrevious = selectedIndex > 0;
135
- const canNext = selectedIndex < totalItems - 1;
136
-
137
- return (
138
- <div ref={ref} className={clsx("flex flex-col", className)} {...rest}>
139
- {children}
140
- {/* Main display area */}
141
- <div className={clsx("relative group")}>
142
- {canPrevious && (
143
- <button
144
- onClick={handlePrevious}
145
- className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
146
- aria-label="Previous slide"
147
- >
148
- <svg
149
- xmlns="http://www.w3.org/2000/svg"
150
- className="h-6 w-6"
151
- fill="none"
152
- viewBox="0 0 24 24"
153
- stroke="currentColor"
154
- >
155
- <path
156
- strokeLinecap="round"
157
- strokeLinejoin="round"
158
- strokeWidth={2}
159
- d="M15 19l-7-7 7-7"
160
- />
161
- </svg>
162
- </button>
163
- )}
164
- {canNext && (
165
- <button
166
- onClick={handleNext}
167
- className="absolute right-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
168
- aria-label="Next slide"
169
- >
170
- <svg
171
- xmlns="http://www.w3.org/2000/svg"
172
- className="h-6 w-6"
173
- fill="none"
174
- viewBox="0 0 24 24"
175
- stroke="currentColor"
176
- >
177
- <path
178
- strokeLinecap="round"
179
- strokeLinejoin="round"
180
- strokeWidth={2}
181
- d="M9 5l7 7-7 7"
182
- />
183
- </svg>
184
- </button>
185
- )}
186
- {value?.externalVideoUrl && selectedId === "video" && (
187
- <div className={clsx("w-full rounded-md overflow-hidden", aspect)}>
188
- <iframe
189
- src={videoUrl}
190
- className="w-full h-full"
191
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; autoplay"
192
- referrerPolicy="strict-origin-when-cross-origin"
193
- allowFullScreen
194
- />
195
- </div>
196
- )}
197
- {value?.medias?.map((media) => (
198
- <div
199
- key={media.id}
200
- className={clsx(
201
- "transition-opacity duration-300 overflow-hidden rounded-md",
202
- media.id === selectedId ? "opacity-100" : "opacity-0 hidden",
203
- aspect
204
- )}
205
- >
206
- <img
207
- src={media?.resize || media?.url}
208
- alt={media?.alt}
209
- className="w-full h-full object-cover object-center"
210
- />
211
- </div>
212
- ))}
213
- </div>
214
-
215
- {/* Thumbnail navigation */}
216
- <div className="relative mt-4">
217
- <div className="flex items-stretch gap-2">
218
- {totalItems > 6 && (
219
- <button
220
- onClick={handlePrevious}
221
- disabled={!canPrevious}
222
- className={clsx(
223
- "flex items-center justify-center w-6",
224
- "transition-colors duration-200 rounded-l-md",
225
- !canPrevious
226
- ? "bg-gray-100 text-gray-400 cursor-not-allowed"
227
- : "bg-gray-200 hover:bg-gray-300 text-gray-700"
228
- )}
229
- aria-label="Previous"
230
- >
231
- <svg
232
- xmlns="http://www.w3.org/2000/svg"
233
- className="h-4 w-4 md:h-5 md:w-5"
234
- fill="none"
235
- viewBox="0 0 24 24"
236
- stroke="currentColor"
237
- >
238
- <path
239
- strokeLinecap="round"
240
- strokeLinejoin="round"
241
- strokeWidth={2}
242
- d="M15 19l-7-7 7-7"
243
- />
244
- </svg>
245
- </button>
246
- )}
247
- <div className="flex-1">
248
- <div className="grid grid-cols-6 gap-2">
249
- {value?.externalVideoUrl && startIndex === 0 && (
250
- <div
251
- className={clsx(
252
- "relative cursor-pointer overflow-hidden rounded-sm border-2",
253
- aspect,
254
- selectedId === "video"
255
- ? "border-primary-500"
256
- : "border-transparent hover:border-primary-300"
257
- )}
258
- onClick={() => setSelectedId("video")}
259
- >
260
- <img
261
- src={thumbnailUrl}
262
- alt="Video thumbnail"
263
- className="w-full h-full object-cover"
264
- />
265
- <div className="absolute inset-0 flex items-center justify-center bg-black/30">
266
- <svg
267
- xmlns="http://www.w3.org/2000/svg"
268
- className="h-8 w-8 text-white"
269
- viewBox="0 0 24 24"
270
- >
271
- <path
272
- fill="currentColor"
273
- fill-rule="evenodd"
274
- d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10"
275
- clipRule="evenodd"
276
- opacity="0.5"
277
- />
278
- <path
279
- fill="currentColor"
280
- d="m15.414 13.059l-4.72 2.787C9.934 16.294 9 15.71 9 14.786V9.214c0-.924.934-1.507 1.694-1.059l4.72 2.787c.781.462.781 1.656 0 2.118"
281
- />
282
- </svg>
283
- </div>
284
- </div>
285
- )}
286
- {value?.medias?.map((media, index) => {
287
- const adjustedIndex = value?.externalVideoUrl
288
- ? index + 1
289
- : index;
290
- return adjustedIndex >= startIndex &&
291
- adjustedIndex < endIndex ? (
292
- <div
293
- key={media.id}
294
- className={clsx(
295
- "relative cursor-pointer overflow-hidden rounded-sm border-2",
296
- aspect,
297
- selectedId === media.id
298
- ? "border-primary-500"
299
- : "border-transparent hover:border-primary-300"
300
- )}
301
- onClick={() => setSelectedId(media.id)}
302
- >
303
- <img
304
- src={media?.resize || media?.url}
305
- alt={media?.alt}
306
- className="w-full h-full object-cover"
307
- />
308
- </div>
309
- ) : null;
310
- })}
311
- </div>
312
- </div>
313
- {totalItems > 6 && (
314
- <button
315
- onClick={handleNext}
316
- disabled={!canNext}
317
- className={clsx(
318
- "flex items-center justify-center w-6",
319
- "transition-colors duration-200 rounded-r-md",
320
- !canNext
321
- ? "bg-gray-100 text-gray-400 cursor-not-allowed"
322
- : "bg-gray-200 hover:bg-gray-300 text-gray-700"
323
- )}
324
- aria-label="Next"
325
- >
326
- <svg
327
- xmlns="http://www.w3.org/2000/svg"
328
- className="h-4 w-4 md:h-5 md:w-5"
329
- fill="none"
330
- viewBox="0 0 24 24"
331
- stroke="currentColor"
332
- >
333
- <path
334
- strokeLinecap="round"
335
- strokeLinejoin="round"
336
- strokeWidth={2}
337
- d="M9 5l7 7-7 7"
338
- />
339
- </svg>
340
- </button>
341
- )}
342
- </div>
343
- </div>
344
- </div>
345
- );
346
- }
347
- );
1
+ import clsx from "clsx";
2
+ import { forwardRef, useEffect, useState, useCallback } from "react";
3
+ import { TMedias } from "../../../entify";
4
+
5
+ export type MediasProps = {
6
+ value?: TMedias;
7
+ className?: string;
8
+ children?: React.ReactNode;
9
+ // Aspect ratio, format is `aspect-[width/height]`
10
+ aspect?: string;
11
+ };
12
+
13
+ export const Medias = forwardRef<HTMLDivElement, MediasProps>(
14
+ (props, ref) => {
15
+ const {
16
+ value,
17
+ className,
18
+ children,
19
+ aspect = "aspect-[5/4]",
20
+ ...rest
21
+ } = props;
22
+ const [selectedId, setSelectedId] = useState<string | undefined | null>(
23
+ value?.externalVideoUrl ? "video" : value?.medias?.[0]?.id || ""
24
+ );
25
+ const [videoUrl, setVideoUrl] = useState<string>("");
26
+ const [thumbnailUrl, setThumbnailUrl] = useState<string>("");
27
+
28
+ useEffect(() => {
29
+ setSelectedId(
30
+ value?.externalVideoUrl ? "video" : value?.medias?.[0]?.id || ""
31
+ );
32
+ }, [value]);
33
+
34
+ useEffect(() => {
35
+ if (value?.externalVideoUrl) {
36
+ const baseUrl = value.externalVideoUrl.replace(
37
+ "https://youtu.be/",
38
+ "https://www.youtube.com/embed/"
39
+ );
40
+ const separator = baseUrl.includes("?") ? "&" : "?";
41
+ setVideoUrl(
42
+ `${baseUrl}${separator}autoplay=1&muted=1&modestbranding=1&rel=0&controls=1&playsinline=1&enablejsapi=1&origin=${encodeURIComponent(
43
+ window.location.origin
44
+ )}`
45
+ );
46
+ }
47
+ }, [value?.externalVideoUrl]);
48
+
49
+ useEffect(() => {
50
+ if (value?.externalVideoUrl) {
51
+ const videoId = value.externalVideoUrl.includes("youtu.be/")
52
+ ? value.externalVideoUrl.split("youtu.be/")[1].split("?")[0]
53
+ : value.externalVideoUrl.split("v=")[1]?.split("&")[0];
54
+
55
+ if (videoId) {
56
+ setThumbnailUrl(
57
+ `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`
58
+ );
59
+ }
60
+ }
61
+ }, [value?.externalVideoUrl]);
62
+
63
+ const selectedIndex = value?.externalVideoUrl
64
+ ? selectedId === "video"
65
+ ? 0
66
+ : (value?.medias?.findIndex((media) => media.id === selectedId) || 0) +
67
+ 1
68
+ : value?.medias?.findIndex((media) => media.id === selectedId) || 0;
69
+
70
+ const totalItems =
71
+ (value?.externalVideoUrl ? 1 : 0) + (value?.medias?.length || 0);
72
+
73
+ // Calculate visible thumbnails (show 6 items)
74
+ const visibleCount = 6;
75
+ const halfVisible = Math.floor(visibleCount / 2);
76
+ const startIndex = Math.max(
77
+ 0,
78
+ Math.min(selectedIndex - halfVisible, totalItems - visibleCount)
79
+ );
80
+ const endIndex = Math.min(startIndex + visibleCount, totalItems);
81
+
82
+ const handlePrevious = useCallback(() => {
83
+ if (selectedIndex > 0) {
84
+ if (value?.externalVideoUrl) {
85
+ if (selectedIndex === 1) {
86
+ setSelectedId("video");
87
+ } else {
88
+ const prevIndex = selectedIndex - 2;
89
+ setSelectedId(value.medias?.[prevIndex]?.id || "");
90
+ }
91
+ } else {
92
+ const prevIndex = selectedIndex - 1;
93
+ setSelectedId(value?.medias?.[prevIndex]?.id || "");
94
+ }
95
+ }
96
+ }, [selectedIndex, value]);
97
+
98
+ const handleNext = useCallback(() => {
99
+ if (selectedIndex < totalItems - 1) {
100
+ if (value?.externalVideoUrl) {
101
+ if (selectedId === "video") {
102
+ setSelectedId(value.medias?.[0]?.id || "");
103
+ } else {
104
+ const currentMediaIndex =
105
+ value.medias?.findIndex((media) => media.id === selectedId) || 0;
106
+ const nextIndex = currentMediaIndex + 1;
107
+ setSelectedId(value.medias?.[nextIndex]?.id || "");
108
+ }
109
+ } else {
110
+ const currentMediaIndex =
111
+ value?.medias?.findIndex((media) => media.id === selectedId) || 0;
112
+ const nextIndex = currentMediaIndex + 1;
113
+ setSelectedId(value?.medias?.[nextIndex]?.id || "");
114
+ }
115
+ }
116
+ }, [selectedIndex, totalItems, value, selectedId]);
117
+
118
+ const handleKeyDown = useCallback(
119
+ (e: KeyboardEvent) => {
120
+ if (e.key === "ArrowLeft") {
121
+ handlePrevious();
122
+ } else if (e.key === "ArrowRight") {
123
+ handleNext();
124
+ }
125
+ },
126
+ [handleNext, handlePrevious]
127
+ );
128
+
129
+ useEffect(() => {
130
+ window.addEventListener("keydown", handleKeyDown);
131
+ return () => window.removeEventListener("keydown", handleKeyDown);
132
+ }, [handleKeyDown]);
133
+
134
+ const canPrevious = selectedIndex > 0;
135
+ const canNext = selectedIndex < totalItems - 1;
136
+
137
+ return (
138
+ <div ref={ref} className={clsx("flex flex-col", className)} {...rest}>
139
+ {children}
140
+ {/* Main display area */}
141
+ <div className={clsx("relative group")}>
142
+ {canPrevious && (
143
+ <button
144
+ onClick={handlePrevious}
145
+ className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
146
+ aria-label="Previous slide"
147
+ >
148
+ <svg
149
+ xmlns="http://www.w3.org/2000/svg"
150
+ className="h-6 w-6"
151
+ fill="none"
152
+ viewBox="0 0 24 24"
153
+ stroke="currentColor"
154
+ >
155
+ <path
156
+ strokeLinecap="round"
157
+ strokeLinejoin="round"
158
+ strokeWidth={2}
159
+ d="M15 19l-7-7 7-7"
160
+ />
161
+ </svg>
162
+ </button>
163
+ )}
164
+ {canNext && (
165
+ <button
166
+ onClick={handleNext}
167
+ className="absolute right-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
168
+ aria-label="Next slide"
169
+ >
170
+ <svg
171
+ xmlns="http://www.w3.org/2000/svg"
172
+ className="h-6 w-6"
173
+ fill="none"
174
+ viewBox="0 0 24 24"
175
+ stroke="currentColor"
176
+ >
177
+ <path
178
+ strokeLinecap="round"
179
+ strokeLinejoin="round"
180
+ strokeWidth={2}
181
+ d="M9 5l7 7-7 7"
182
+ />
183
+ </svg>
184
+ </button>
185
+ )}
186
+ {value?.externalVideoUrl && selectedId === "video" && (
187
+ <div className={clsx("w-full rounded-md overflow-hidden", aspect)}>
188
+ <iframe
189
+ src={videoUrl}
190
+ className="w-full h-full"
191
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; autoplay"
192
+ referrerPolicy="strict-origin-when-cross-origin"
193
+ allowFullScreen
194
+ />
195
+ </div>
196
+ )}
197
+ {value?.medias?.map((media) => (
198
+ <div
199
+ key={media.id}
200
+ className={clsx(
201
+ "transition-opacity duration-300 overflow-hidden rounded-md",
202
+ media.id === selectedId ? "opacity-100" : "opacity-0 hidden",
203
+ aspect
204
+ )}
205
+ >
206
+ <img
207
+ src={media?.resize || media?.url}
208
+ alt={media?.alt}
209
+ className="w-full h-full object-cover object-center"
210
+ />
211
+ </div>
212
+ ))}
213
+ </div>
214
+
215
+ {/* Thumbnail navigation */}
216
+ <div className="relative mt-4">
217
+ <div className="flex items-stretch gap-2">
218
+ {totalItems > 6 && (
219
+ <button
220
+ onClick={handlePrevious}
221
+ disabled={!canPrevious}
222
+ className={clsx(
223
+ "flex items-center justify-center w-6",
224
+ "transition-colors duration-200 rounded-l-md",
225
+ !canPrevious
226
+ ? "bg-gray-100 text-gray-400 cursor-not-allowed"
227
+ : "bg-gray-200 hover:bg-gray-300 text-gray-700"
228
+ )}
229
+ aria-label="Previous"
230
+ >
231
+ <svg
232
+ xmlns="http://www.w3.org/2000/svg"
233
+ className="h-4 w-4 md:h-5 md:w-5"
234
+ fill="none"
235
+ viewBox="0 0 24 24"
236
+ stroke="currentColor"
237
+ >
238
+ <path
239
+ strokeLinecap="round"
240
+ strokeLinejoin="round"
241
+ strokeWidth={2}
242
+ d="M15 19l-7-7 7-7"
243
+ />
244
+ </svg>
245
+ </button>
246
+ )}
247
+ <div className="flex-1">
248
+ <div className="grid grid-cols-6 gap-2">
249
+ {value?.externalVideoUrl && startIndex === 0 && (
250
+ <div
251
+ className={clsx(
252
+ "relative cursor-pointer overflow-hidden rounded-sm border-2",
253
+ aspect,
254
+ selectedId === "video"
255
+ ? "border-primary-500"
256
+ : "border-transparent hover:border-primary-300"
257
+ )}
258
+ onClick={() => setSelectedId("video")}
259
+ >
260
+ <img
261
+ src={thumbnailUrl}
262
+ alt="Video thumbnail"
263
+ className="w-full h-full object-cover"
264
+ />
265
+ <div className="absolute inset-0 flex items-center justify-center bg-black/30">
266
+ <svg
267
+ xmlns="http://www.w3.org/2000/svg"
268
+ className="h-8 w-8 text-white"
269
+ viewBox="0 0 24 24"
270
+ >
271
+ <path
272
+ fill="currentColor"
273
+ fill-rule="evenodd"
274
+ d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10"
275
+ clipRule="evenodd"
276
+ opacity="0.5"
277
+ />
278
+ <path
279
+ fill="currentColor"
280
+ d="m15.414 13.059l-4.72 2.787C9.934 16.294 9 15.71 9 14.786V9.214c0-.924.934-1.507 1.694-1.059l4.72 2.787c.781.462.781 1.656 0 2.118"
281
+ />
282
+ </svg>
283
+ </div>
284
+ </div>
285
+ )}
286
+ {value?.medias?.map((media, index) => {
287
+ const adjustedIndex = value?.externalVideoUrl
288
+ ? index + 1
289
+ : index;
290
+ return adjustedIndex >= startIndex &&
291
+ adjustedIndex < endIndex ? (
292
+ <div
293
+ key={media.id}
294
+ className={clsx(
295
+ "relative cursor-pointer overflow-hidden rounded-sm border-2",
296
+ aspect,
297
+ selectedId === media.id
298
+ ? "border-primary-500"
299
+ : "border-transparent hover:border-primary-300"
300
+ )}
301
+ onClick={() => setSelectedId(media.id)}
302
+ >
303
+ <img
304
+ src={media?.resize || media?.url}
305
+ alt={media?.alt}
306
+ className="w-full h-full object-cover"
307
+ />
308
+ </div>
309
+ ) : null;
310
+ })}
311
+ </div>
312
+ </div>
313
+ {totalItems > 6 && (
314
+ <button
315
+ onClick={handleNext}
316
+ disabled={!canNext}
317
+ className={clsx(
318
+ "flex items-center justify-center w-6",
319
+ "transition-colors duration-200 rounded-r-md",
320
+ !canNext
321
+ ? "bg-gray-100 text-gray-400 cursor-not-allowed"
322
+ : "bg-gray-200 hover:bg-gray-300 text-gray-700"
323
+ )}
324
+ aria-label="Next"
325
+ >
326
+ <svg
327
+ xmlns="http://www.w3.org/2000/svg"
328
+ className="h-4 w-4 md:h-5 md:w-5"
329
+ fill="none"
330
+ viewBox="0 0 24 24"
331
+ stroke="currentColor"
332
+ >
333
+ <path
334
+ strokeLinecap="round"
335
+ strokeLinejoin="round"
336
+ strokeWidth={2}
337
+ d="M9 5l7 7-7 7"
338
+ />
339
+ </svg>
340
+ </button>
341
+ )}
342
+ </div>
343
+ </div>
344
+ </div>
345
+ );
346
+ }
347
+ );