analytica-frontend-lib 1.1.77 → 1.1.78

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.
@@ -1,8 +1,8 @@
1
1
  // src/components/VideoPlayer/VideoPlayer.tsx
2
2
  import {
3
3
  useRef,
4
- useState,
5
- useEffect,
4
+ useState as useState2,
5
+ useEffect as useEffect2,
6
6
  useCallback
7
7
  } from "react";
8
8
  import { createPortal } from "react-dom";
@@ -128,6 +128,86 @@ var Text = ({
128
128
  };
129
129
  var Text_default = Text;
130
130
 
131
+ // src/hooks/useMobile.ts
132
+ import { useState, useEffect } from "react";
133
+ var MOBILE_WIDTH = 500;
134
+ var TABLET_WIDTH = 931;
135
+ var SMALL_MOBILE_WIDTH = 425;
136
+ var EXTRA_SMALL_MOBILE_WIDTH = 375;
137
+ var ULTRA_SMALL_MOBILE_WIDTH = 375;
138
+ var TINY_MOBILE_WIDTH = 320;
139
+ var DEFAULT_WIDTH = 1200;
140
+ var getWindowWidth = () => {
141
+ if (typeof window === "undefined") {
142
+ return DEFAULT_WIDTH;
143
+ }
144
+ return window.innerWidth;
145
+ };
146
+ var getDeviceType = () => {
147
+ const width = getWindowWidth();
148
+ return width < TABLET_WIDTH ? "responsive" : "desktop";
149
+ };
150
+ var useMobile = () => {
151
+ const [isMobile, setIsMobile] = useState(false);
152
+ const [isTablet, setIsTablet] = useState(false);
153
+ const [isSmallMobile, setIsSmallMobile] = useState(false);
154
+ const [isExtraSmallMobile, setIsExtraSmallMobile] = useState(false);
155
+ const [isUltraSmallMobile, setIsUltraSmallMobile] = useState(false);
156
+ const [isTinyMobile, setIsTinyMobile] = useState(false);
157
+ useEffect(() => {
158
+ const checkScreenSize = () => {
159
+ const width = getWindowWidth();
160
+ setIsMobile(width < MOBILE_WIDTH);
161
+ setIsTablet(width < TABLET_WIDTH);
162
+ setIsSmallMobile(width < SMALL_MOBILE_WIDTH);
163
+ setIsExtraSmallMobile(width < EXTRA_SMALL_MOBILE_WIDTH);
164
+ setIsUltraSmallMobile(width < ULTRA_SMALL_MOBILE_WIDTH);
165
+ setIsTinyMobile(width < TINY_MOBILE_WIDTH);
166
+ };
167
+ checkScreenSize();
168
+ window.addEventListener("resize", checkScreenSize);
169
+ return () => window.removeEventListener("resize", checkScreenSize);
170
+ }, []);
171
+ const getFormContainerClasses = () => {
172
+ if (isMobile) {
173
+ return "w-full px-4";
174
+ }
175
+ if (isTablet) {
176
+ return "w-full px-6";
177
+ }
178
+ return "w-full max-w-[992px] mx-auto px-0";
179
+ };
180
+ const getMobileHeaderClasses = () => {
181
+ return "flex flex-col items-start gap-4 mb-6";
182
+ };
183
+ const getDesktopHeaderClasses = () => {
184
+ return "flex flex-row justify-between items-center gap-6 mb-8";
185
+ };
186
+ const getHeaderClasses = () => {
187
+ return isMobile ? getMobileHeaderClasses() : getDesktopHeaderClasses();
188
+ };
189
+ const getVideoContainerClasses = () => {
190
+ if (isTinyMobile) return "aspect-square";
191
+ if (isExtraSmallMobile) return "aspect-[4/3]";
192
+ if (isSmallMobile) return "aspect-[16/12]";
193
+ return "aspect-video";
194
+ };
195
+ return {
196
+ isMobile,
197
+ isTablet,
198
+ isSmallMobile,
199
+ isExtraSmallMobile,
200
+ isUltraSmallMobile,
201
+ isTinyMobile,
202
+ getFormContainerClasses,
203
+ getHeaderClasses,
204
+ getMobileHeaderClasses,
205
+ getDesktopHeaderClasses,
206
+ getVideoContainerClasses,
207
+ getDeviceType
208
+ };
209
+ };
210
+
131
211
  // src/components/VideoPlayer/VideoPlayer.tsx
132
212
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
133
213
  var CONTROLS_HIDE_TIMEOUT = 3e3;
@@ -143,8 +223,9 @@ var ProgressBar = ({
143
223
  currentTime,
144
224
  duration,
145
225
  progressPercentage,
146
- onSeek
147
- }) => /* @__PURE__ */ jsx3("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx3(
226
+ onSeek,
227
+ className = "px-4 pb-2"
228
+ }) => /* @__PURE__ */ jsx3("div", { className, children: /* @__PURE__ */ jsx3(
148
229
  "input",
149
230
  {
150
231
  type: "range",
@@ -163,18 +244,20 @@ var VolumeControls = ({
163
244
  volume,
164
245
  isMuted,
165
246
  onVolumeChange,
166
- onToggleMute
247
+ onToggleMute,
248
+ iconSize = 24,
249
+ showSlider = true
167
250
  }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
168
251
  /* @__PURE__ */ jsx3(
169
252
  IconButton_default,
170
253
  {
171
- icon: isMuted ? /* @__PURE__ */ jsx3(SpeakerSlash, { size: 24 }) : /* @__PURE__ */ jsx3(SpeakerHigh, { size: 24 }),
254
+ icon: isMuted ? /* @__PURE__ */ jsx3(SpeakerSlash, { size: iconSize }) : /* @__PURE__ */ jsx3(SpeakerHigh, { size: iconSize }),
172
255
  onClick: onToggleMute,
173
256
  "aria-label": isMuted ? "Unmute" : "Mute",
174
257
  className: "!bg-transparent !text-white hover:!bg-white/20"
175
258
  }
176
259
  ),
177
- /* @__PURE__ */ jsx3(
260
+ showSlider && /* @__PURE__ */ jsx3(
178
261
  "input",
179
262
  {
180
263
  type: "range",
@@ -195,7 +278,9 @@ var SpeedMenu = ({
195
278
  playbackRate,
196
279
  onToggleMenu,
197
280
  onSpeedChange,
198
- isFullscreen
281
+ isFullscreen,
282
+ iconSize = 24,
283
+ isTinyMobile = false
199
284
  }) => {
200
285
  const buttonRef = useRef(null);
201
286
  const speedMenuContainerRef = useRef(null);
@@ -203,14 +288,17 @@ var SpeedMenu = ({
203
288
  const getMenuPosition = () => {
204
289
  if (!buttonRef.current) return { top: 0, left: 0 };
205
290
  const rect = buttonRef.current.getBoundingClientRect();
291
+ const menuHeight = isTinyMobile ? 150 : 180;
292
+ const menuWidth = isTinyMobile ? 60 : 80;
293
+ const padding = isTinyMobile ? 4 : 8;
206
294
  return {
207
295
  // Fixed coords are viewport-based — no scroll offsets.
208
- top: Math.max(8, rect.top - 180),
209
- left: Math.max(8, rect.right - 80)
296
+ top: Math.max(padding, rect.top - menuHeight),
297
+ left: Math.max(padding, rect.right - menuWidth)
210
298
  };
211
299
  };
212
300
  const position = getMenuPosition();
213
- useEffect(() => {
301
+ useEffect2(() => {
214
302
  const handleClickOutside = (event) => {
215
303
  const target = event.target;
216
304
  const isOutsideContainer = speedMenuContainerRef.current && !speedMenuContainerRef.current.contains(target);
@@ -259,7 +347,7 @@ var SpeedMenu = ({
259
347
  IconButton_default,
260
348
  {
261
349
  ref: buttonRef,
262
- icon: /* @__PURE__ */ jsx3(DotsThreeVertical, { size: 24 }),
350
+ icon: /* @__PURE__ */ jsx3(DotsThreeVertical, { size: iconSize }),
263
351
  onClick: onToggleMenu,
264
352
  "aria-label": "Playback speed",
265
353
  "aria-haspopup": "menu",
@@ -285,20 +373,21 @@ var VideoPlayer = ({
285
373
  storageKey = "video-progress"
286
374
  }) => {
287
375
  const videoRef = useRef(null);
288
- const [isPlaying, setIsPlaying] = useState(false);
289
- const [currentTime, setCurrentTime] = useState(0);
290
- const [duration, setDuration] = useState(0);
291
- const [isMuted, setIsMuted] = useState(false);
292
- const [volume, setVolume] = useState(1);
293
- const [isFullscreen, setIsFullscreen] = useState(false);
294
- const [showControls, setShowControls] = useState(true);
295
- const [hasCompleted, setHasCompleted] = useState(false);
296
- const [showCaptions, setShowCaptions] = useState(false);
297
- useEffect(() => {
376
+ const { isUltraSmallMobile, isTinyMobile } = useMobile();
377
+ const [isPlaying, setIsPlaying] = useState2(false);
378
+ const [currentTime, setCurrentTime] = useState2(0);
379
+ const [duration, setDuration] = useState2(0);
380
+ const [isMuted, setIsMuted] = useState2(false);
381
+ const [volume, setVolume] = useState2(1);
382
+ const [isFullscreen, setIsFullscreen] = useState2(false);
383
+ const [showControls, setShowControls] = useState2(true);
384
+ const [hasCompleted, setHasCompleted] = useState2(false);
385
+ const [showCaptions, setShowCaptions] = useState2(false);
386
+ useEffect2(() => {
298
387
  setHasCompleted(false);
299
388
  }, [src]);
300
- const [playbackRate, setPlaybackRate] = useState(1);
301
- const [showSpeedMenu, setShowSpeedMenu] = useState(false);
389
+ const [playbackRate, setPlaybackRate] = useState2(1);
390
+ const [showSpeedMenu, setShowSpeedMenu] = useState2(false);
302
391
  const lastSaveTimeRef = useRef(0);
303
392
  const trackRef = useRef(null);
304
393
  const controlsTimeoutRef = useRef(null);
@@ -366,13 +455,13 @@ var VideoPlayer = ({
366
455
  }, LEAVE_HIDE_TIMEOUT);
367
456
  }
368
457
  }, [isFullscreen, clearControlsTimeout, isUserInteracting]);
369
- useEffect(() => {
458
+ useEffect2(() => {
370
459
  if (videoRef.current) {
371
460
  videoRef.current.volume = volume;
372
461
  videoRef.current.muted = isMuted;
373
462
  }
374
463
  }, [volume, isMuted]);
375
- useEffect(() => {
464
+ useEffect2(() => {
376
465
  const video = videoRef.current;
377
466
  if (!video) return;
378
467
  const onPlay = () => setIsPlaying(true);
@@ -387,7 +476,13 @@ var VideoPlayer = ({
387
476
  video.removeEventListener("ended", onEnded);
388
477
  };
389
478
  }, []);
390
- useEffect(() => {
479
+ useEffect2(() => {
480
+ const video = videoRef.current;
481
+ if (!video) return;
482
+ video.setAttribute("playsinline", "");
483
+ video.setAttribute("webkit-playsinline", "");
484
+ }, []);
485
+ useEffect2(() => {
391
486
  if (isPlaying) {
392
487
  showControlsWithTimer();
393
488
  } else {
@@ -399,7 +494,7 @@ var VideoPlayer = ({
399
494
  }
400
495
  }
401
496
  }, [isPlaying, isFullscreen, showControlsWithTimer, clearControlsTimeout]);
402
- useEffect(() => {
497
+ useEffect2(() => {
403
498
  const handleFullscreenChange = () => {
404
499
  const isCurrentlyFullscreen = !!document.fullscreenElement;
405
500
  setIsFullscreen(isCurrentlyFullscreen);
@@ -412,7 +507,7 @@ var VideoPlayer = ({
412
507
  document.removeEventListener("fullscreenchange", handleFullscreenChange);
413
508
  };
414
509
  }, [showControlsWithTimer]);
415
- useEffect(() => {
510
+ useEffect2(() => {
416
511
  const init = () => {
417
512
  if (!isFullscreen) {
418
513
  showControlsWithTimer();
@@ -445,7 +540,7 @@ var VideoPlayer = ({
445
540
  if (hasValidSaved) return saved;
446
541
  return void 0;
447
542
  }, [autoSave, storageKey, src, initialTime]);
448
- useEffect(() => {
543
+ useEffect2(() => {
449
544
  const start = getInitialTime();
450
545
  if (start !== void 0 && videoRef.current) {
451
546
  videoRef.current.currentTime = start;
@@ -566,12 +661,12 @@ var VideoPlayer = ({
566
661
  setDuration(videoRef.current.duration);
567
662
  }
568
663
  }, []);
569
- useEffect(() => {
664
+ useEffect2(() => {
570
665
  if (trackRef.current?.track) {
571
666
  trackRef.current.track.mode = showCaptions && subtitles ? "showing" : "hidden";
572
667
  }
573
668
  }, [subtitles, showCaptions]);
574
- useEffect(() => {
669
+ useEffect2(() => {
575
670
  const handleVisibilityChange = () => {
576
671
  if (document.hidden && isPlaying && videoRef.current) {
577
672
  videoRef.current.pause();
@@ -593,6 +688,31 @@ var VideoPlayer = ({
593
688
  };
594
689
  }, [isPlaying, clearControlsTimeout]);
595
690
  const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
691
+ const getIconSize = useCallback(() => {
692
+ if (isTinyMobile) return 18;
693
+ if (isUltraSmallMobile) return 20;
694
+ return 24;
695
+ }, [isTinyMobile, isUltraSmallMobile]);
696
+ const getControlsPadding = useCallback(() => {
697
+ if (isTinyMobile) return "px-2 pb-2 pt-1";
698
+ if (isUltraSmallMobile) return "px-3 pb-3 pt-1";
699
+ return "px-4 pb-4";
700
+ }, [isTinyMobile, isUltraSmallMobile]);
701
+ const getControlsGap = useCallback(() => {
702
+ if (isTinyMobile) return "gap-1";
703
+ if (isUltraSmallMobile) return "gap-2";
704
+ return "gap-4";
705
+ }, [isTinyMobile, isUltraSmallMobile]);
706
+ const getProgressBarPadding = useCallback(() => {
707
+ if (isTinyMobile) return "px-2 pb-1";
708
+ if (isUltraSmallMobile) return "px-3 pb-1";
709
+ return "px-4 pb-2";
710
+ }, [isTinyMobile, isUltraSmallMobile]);
711
+ const getCenterPlayButtonPosition = useCallback(() => {
712
+ if (isTinyMobile) return "items-center justify-center -translate-y-12";
713
+ if (isUltraSmallMobile) return "items-center justify-center -translate-y-8";
714
+ return "items-center justify-center";
715
+ }, [isTinyMobile, isUltraSmallMobile]);
596
716
  const getTopControlsOpacity = useCallback(() => {
597
717
  return showControls ? "opacity-100" : "opacity-0";
598
718
  }, [showControls]);
@@ -700,8 +820,9 @@ var VideoPlayer = ({
700
820
  ref: videoRef,
701
821
  src,
702
822
  poster,
703
- className: "w-full h-full object-contain",
823
+ className: "w-full h-full object-contain analytica-video",
704
824
  controlsList: "nodownload",
825
+ playsInline: true,
705
826
  onTimeUpdate: handleTimeUpdate,
706
827
  onLoadedMetadata: handleLoadedMetadata,
707
828
  onClick: togglePlayPause,
@@ -721,15 +842,24 @@ var VideoPlayer = ({
721
842
  )
722
843
  }
723
844
  ),
724
- !isPlaying && /* @__PURE__ */ jsx3("div", { className: "absolute inset-0 flex items-center justify-center bg-black/30 transition-opacity", children: /* @__PURE__ */ jsx3(
725
- IconButton_default,
845
+ !isPlaying && /* @__PURE__ */ jsx3(
846
+ "div",
726
847
  {
727
- icon: /* @__PURE__ */ jsx3(Play, { size: 32, weight: "regular", className: "ml-1" }),
728
- onClick: togglePlayPause,
729
- "aria-label": "Play video",
730
- className: "!bg-transparent !text-white !w-auto !h-auto hover:!bg-transparent hover:!text-gray-200"
848
+ className: cn(
849
+ "absolute inset-0 flex bg-black/30 transition-opacity",
850
+ getCenterPlayButtonPosition()
851
+ ),
852
+ children: /* @__PURE__ */ jsx3(
853
+ IconButton_default,
854
+ {
855
+ icon: /* @__PURE__ */ jsx3(Play, { size: 32, weight: "regular", className: "ml-1" }),
856
+ onClick: togglePlayPause,
857
+ "aria-label": "Play video",
858
+ className: "!bg-transparent !text-white !w-auto !h-auto hover:!bg-transparent hover:!text-gray-200"
859
+ }
860
+ )
731
861
  }
732
- ) }),
862
+ ),
733
863
  /* @__PURE__ */ jsx3(
734
864
  "div",
735
865
  {
@@ -762,58 +892,72 @@ var VideoPlayer = ({
762
892
  currentTime,
763
893
  duration,
764
894
  progressPercentage,
765
- onSeek: handleSeek
895
+ onSeek: handleSeek,
896
+ className: getProgressBarPadding()
766
897
  }
767
898
  ),
768
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 pb-4", children: [
769
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
770
- /* @__PURE__ */ jsx3(
771
- IconButton_default,
772
- {
773
- icon: isPlaying ? /* @__PURE__ */ jsx3(Pause, { size: 24 }) : /* @__PURE__ */ jsx3(Play, { size: 24 }),
774
- onClick: togglePlayPause,
775
- "aria-label": isPlaying ? "Pause" : "Play",
776
- className: "!bg-transparent !text-white hover:!bg-white/20"
777
- }
778
- ),
779
- /* @__PURE__ */ jsx3(
780
- VolumeControls,
781
- {
782
- volume,
783
- isMuted,
784
- onVolumeChange: handleVolumeChange,
785
- onToggleMute: toggleMute
786
- }
787
- ),
788
- subtitles && /* @__PURE__ */ jsx3(
789
- IconButton_default,
790
- {
791
- icon: /* @__PURE__ */ jsx3(ClosedCaptioning, { size: 24 }),
792
- onClick: toggleCaptions,
793
- "aria-label": showCaptions ? "Hide captions" : "Show captions",
794
- className: cn(
795
- "!bg-transparent hover:!bg-white/20",
796
- showCaptions ? "!text-primary-400" : "!text-white"
797
- )
798
- }
899
+ /* @__PURE__ */ jsxs(
900
+ "div",
901
+ {
902
+ className: cn(
903
+ "flex items-center justify-between",
904
+ getControlsPadding()
799
905
  ),
800
- /* @__PURE__ */ jsxs(Text_default, { size: "sm", weight: "medium", color: "text-white", children: [
801
- formatTime(currentTime),
802
- " / ",
803
- formatTime(duration)
804
- ] })
805
- ] }),
806
- /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx3(
807
- SpeedMenu,
808
- {
809
- showSpeedMenu,
810
- playbackRate,
811
- onToggleMenu: toggleSpeedMenu,
812
- onSpeedChange: handleSpeedChange,
813
- isFullscreen
814
- }
815
- ) })
816
- ] })
906
+ children: [
907
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", getControlsGap()), children: [
908
+ /* @__PURE__ */ jsx3(
909
+ IconButton_default,
910
+ {
911
+ icon: isPlaying ? /* @__PURE__ */ jsx3(Pause, { size: getIconSize() }) : /* @__PURE__ */ jsx3(Play, { size: getIconSize() }),
912
+ onClick: togglePlayPause,
913
+ "aria-label": isPlaying ? "Pause" : "Play",
914
+ className: "!bg-transparent !text-white hover:!bg-white/20"
915
+ }
916
+ ),
917
+ /* @__PURE__ */ jsx3(
918
+ VolumeControls,
919
+ {
920
+ volume,
921
+ isMuted,
922
+ onVolumeChange: handleVolumeChange,
923
+ onToggleMute: toggleMute,
924
+ iconSize: getIconSize(),
925
+ showSlider: !isUltraSmallMobile
926
+ }
927
+ ),
928
+ subtitles && /* @__PURE__ */ jsx3(
929
+ IconButton_default,
930
+ {
931
+ icon: /* @__PURE__ */ jsx3(ClosedCaptioning, { size: getIconSize() }),
932
+ onClick: toggleCaptions,
933
+ "aria-label": showCaptions ? "Hide captions" : "Show captions",
934
+ className: cn(
935
+ "!bg-transparent hover:!bg-white/20",
936
+ showCaptions ? "!text-primary-400" : "!text-white"
937
+ )
938
+ }
939
+ ),
940
+ /* @__PURE__ */ jsxs(Text_default, { size: "sm", weight: "medium", color: "text-white", children: [
941
+ formatTime(currentTime),
942
+ " / ",
943
+ formatTime(duration)
944
+ ] })
945
+ ] }),
946
+ /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx3(
947
+ SpeedMenu,
948
+ {
949
+ showSpeedMenu,
950
+ playbackRate,
951
+ onToggleMenu: toggleSpeedMenu,
952
+ onSpeedChange: handleSpeedChange,
953
+ iconSize: getIconSize(),
954
+ isTinyMobile,
955
+ isFullscreen
956
+ }
957
+ ) })
958
+ ]
959
+ }
960
+ )
817
961
  ]
818
962
  }
819
963
  )