@alfalab/core-components-gallery 5.7.4 → 5.8.0

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 (258) hide show
  1. package/Component.js +47 -12
  2. package/components/buttons/index.css +35 -0
  3. package/{buttons-ec113bb4.d.ts → components/buttons/index.d.ts} +6 -1
  4. package/components/buttons/index.js +72 -0
  5. package/components/header/Component.js +33 -12
  6. package/components/header/index.css +5 -19
  7. package/components/header-info-block/Component.js +1 -1
  8. package/components/header-info-block/index.css +5 -5
  9. package/components/header-mobile/Component.d.ts +4 -0
  10. package/components/header-mobile/Component.js +39 -0
  11. package/components/header-mobile/index.css +50 -0
  12. package/components/header-mobile/index.d.ts +1 -0
  13. package/components/header-mobile/index.js +9 -0
  14. package/components/image-preview/Component.js +67 -24
  15. package/components/image-preview/index.css +35 -28
  16. package/components/image-preview/paths.d.ts +2 -0
  17. package/components/image-preview/paths.js +2 -0
  18. package/components/image-viewer/component.js +30 -10
  19. package/components/image-viewer/index.css +37 -25
  20. package/components/image-viewer/slide.js +3 -1
  21. package/components/image-viewer/video/index.css +48 -0
  22. package/components/image-viewer/video/index.d.ts +10 -0
  23. package/components/image-viewer/video/index.js +130 -0
  24. package/components/index.d.ts +2 -0
  25. package/components/index.js +4 -0
  26. package/components/info-bar/Component.d.ts +4 -0
  27. package/components/info-bar/Component.js +47 -0
  28. package/components/info-bar/index.css +37 -0
  29. package/components/info-bar/index.d.ts +1 -0
  30. package/components/info-bar/index.js +9 -0
  31. package/components/navigation-bar/Component.js +3 -2
  32. package/components/navigation-bar/index.css +8 -6
  33. package/context.d.ts +7 -0
  34. package/context.js +7 -0
  35. package/cssm/Component.js +46 -11
  36. package/cssm/components/{header/buttons.d.ts → buttons/index.d.ts} +6 -1
  37. package/cssm/components/buttons/index.js +71 -0
  38. package/cssm/components/buttons/index.module.css +34 -0
  39. package/cssm/components/header/Component.js +28 -10
  40. package/cssm/components/header/index.module.css +3 -17
  41. package/cssm/components/header-mobile/Component.d.ts +4 -0
  42. package/cssm/components/header-mobile/Component.js +38 -0
  43. package/cssm/components/header-mobile/index.d.ts +1 -0
  44. package/cssm/components/header-mobile/index.js +9 -0
  45. package/cssm/components/header-mobile/index.module.css +49 -0
  46. package/cssm/components/image-preview/Component.js +66 -27
  47. package/cssm/components/image-preview/index.module.css +25 -18
  48. package/cssm/components/image-preview/paths.d.ts +2 -0
  49. package/cssm/components/image-preview/paths.js +2 -0
  50. package/cssm/components/image-viewer/component.js +29 -9
  51. package/cssm/components/image-viewer/index.module.css +21 -9
  52. package/cssm/components/image-viewer/slide.js +12 -2
  53. package/cssm/components/image-viewer/video/index.d.ts +10 -0
  54. package/cssm/components/image-viewer/video/index.js +129 -0
  55. package/cssm/components/image-viewer/video/index.module.css +47 -0
  56. package/cssm/components/index.d.ts +2 -0
  57. package/cssm/components/index.js +4 -0
  58. package/cssm/components/info-bar/Component.d.ts +4 -0
  59. package/cssm/components/info-bar/Component.js +46 -0
  60. package/cssm/components/info-bar/index.d.ts +1 -0
  61. package/cssm/components/info-bar/index.js +9 -0
  62. package/cssm/components/info-bar/index.module.css +36 -0
  63. package/cssm/components/navigation-bar/Component.js +2 -1
  64. package/cssm/components/navigation-bar/index.module.css +3 -1
  65. package/cssm/context.d.ts +7 -0
  66. package/cssm/context.js +7 -0
  67. package/cssm/index.d.ts +1 -1
  68. package/cssm/index.js +1 -8
  69. package/cssm/index.module.css +14 -2
  70. package/cssm/types.d.ts +7 -0
  71. package/cssm/utils/constants.d.ts +14 -1
  72. package/cssm/utils/constants.js +19 -0
  73. package/cssm/utils/index.js +7 -0
  74. package/cssm/utils/utils.d.ts +2 -1
  75. package/cssm/utils/utils.js +5 -3
  76. package/esm/Component.js +46 -12
  77. package/esm/components/buttons/index.css +35 -0
  78. package/esm/{buttons-791da71e.d.ts → components/buttons/index.d.ts} +6 -1
  79. package/esm/components/buttons/index.js +55 -0
  80. package/esm/components/header/Component.js +30 -9
  81. package/esm/components/header/index.css +5 -19
  82. package/esm/components/header-info-block/Component.js +1 -1
  83. package/esm/components/header-info-block/index.css +5 -5
  84. package/esm/components/header-mobile/Component.d.ts +4 -0
  85. package/esm/components/header-mobile/Component.js +30 -0
  86. package/esm/components/header-mobile/index.css +50 -0
  87. package/esm/components/header-mobile/index.d.ts +1 -0
  88. package/esm/components/header-mobile/index.js +1 -0
  89. package/esm/components/image-preview/Component.js +69 -26
  90. package/esm/components/image-preview/index.css +35 -28
  91. package/esm/components/image-preview/paths.d.ts +2 -0
  92. package/esm/components/image-preview/paths.js +2 -0
  93. package/esm/components/image-viewer/component.js +31 -11
  94. package/esm/components/image-viewer/index.css +37 -25
  95. package/esm/components/image-viewer/slide.js +3 -1
  96. package/esm/components/image-viewer/video/index.css +48 -0
  97. package/esm/components/image-viewer/video/index.d.ts +10 -0
  98. package/esm/components/image-viewer/video/index.js +119 -0
  99. package/esm/components/index.d.ts +2 -0
  100. package/esm/components/index.js +2 -0
  101. package/esm/components/info-bar/Component.d.ts +4 -0
  102. package/esm/components/info-bar/Component.js +39 -0
  103. package/esm/components/info-bar/index.css +37 -0
  104. package/esm/components/info-bar/index.d.ts +1 -0
  105. package/esm/components/info-bar/index.js +1 -0
  106. package/esm/components/navigation-bar/Component.js +3 -2
  107. package/esm/components/navigation-bar/index.css +8 -6
  108. package/esm/context.d.ts +7 -0
  109. package/esm/context.js +7 -0
  110. package/esm/index.css +17 -5
  111. package/esm/index.d.ts +1 -1
  112. package/esm/index.js +1 -3
  113. package/esm/{slide-7d5a41d1.js → slide-c469a906.js} +15 -5
  114. package/esm/types.d.ts +7 -0
  115. package/esm/utils/constants.d.ts +14 -1
  116. package/esm/utils/constants.js +14 -1
  117. package/esm/utils/index.js +2 -2
  118. package/esm/utils/utils.d.ts +2 -1
  119. package/esm/utils/utils.js +5 -4
  120. package/index.css +17 -5
  121. package/index.d.ts +1 -1
  122. package/index.js +1 -8
  123. package/modern/Component.js +39 -7
  124. package/modern/components/buttons/index.css +35 -0
  125. package/modern/{buttons-1859cb8e.d.ts → components/buttons/index.d.ts} +6 -1
  126. package/modern/components/buttons/index.js +33 -0
  127. package/modern/components/header/Component.js +27 -9
  128. package/modern/components/header/index.css +5 -19
  129. package/modern/components/header-info-block/Component.js +1 -1
  130. package/modern/components/header-info-block/index.css +5 -5
  131. package/modern/components/header-mobile/Component.d.ts +4 -0
  132. package/modern/components/header-mobile/Component.js +28 -0
  133. package/modern/components/header-mobile/index.css +50 -0
  134. package/modern/components/header-mobile/index.d.ts +1 -0
  135. package/modern/components/header-mobile/index.js +1 -0
  136. package/modern/components/image-preview/Component.js +65 -23
  137. package/modern/components/image-preview/index.css +35 -28
  138. package/modern/components/image-preview/paths.d.ts +2 -0
  139. package/modern/components/image-preview/paths.js +2 -0
  140. package/modern/components/image-viewer/component.js +31 -11
  141. package/modern/components/image-viewer/index.css +37 -25
  142. package/modern/components/image-viewer/slide.js +3 -1
  143. package/modern/components/image-viewer/video/index.css +48 -0
  144. package/modern/components/image-viewer/video/index.d.ts +10 -0
  145. package/modern/components/image-viewer/video/index.js +117 -0
  146. package/modern/components/index.d.ts +2 -0
  147. package/modern/components/index.js +2 -0
  148. package/modern/components/info-bar/Component.d.ts +4 -0
  149. package/modern/components/info-bar/Component.js +38 -0
  150. package/modern/components/info-bar/index.css +37 -0
  151. package/modern/components/info-bar/index.d.ts +1 -0
  152. package/modern/components/info-bar/index.js +1 -0
  153. package/modern/components/navigation-bar/Component.js +3 -2
  154. package/modern/components/navigation-bar/index.css +8 -6
  155. package/modern/context.d.ts +7 -0
  156. package/modern/context.js +7 -0
  157. package/modern/index.css +17 -5
  158. package/modern/index.d.ts +1 -1
  159. package/modern/index.js +1 -3
  160. package/modern/{slide-c47386c3.js → slide-83826e9d.js} +15 -5
  161. package/modern/types.d.ts +7 -0
  162. package/modern/utils/constants.d.ts +14 -1
  163. package/modern/utils/constants.js +14 -1
  164. package/modern/utils/index.js +2 -2
  165. package/modern/utils/utils.d.ts +2 -1
  166. package/modern/utils/utils.js +5 -4
  167. package/moderncssm/Component.js +38 -6
  168. package/moderncssm/components/buttons/index.d.ts +16 -0
  169. package/moderncssm/components/buttons/index.js +31 -0
  170. package/moderncssm/components/buttons/index.module.css +20 -0
  171. package/moderncssm/components/header/Component.js +24 -9
  172. package/moderncssm/components/header/index.module.css +0 -19
  173. package/moderncssm/components/header-mobile/Component.d.ts +4 -0
  174. package/moderncssm/components/header-mobile/Component.js +26 -0
  175. package/moderncssm/components/header-mobile/index.d.ts +1 -0
  176. package/moderncssm/components/header-mobile/index.js +1 -0
  177. package/moderncssm/components/header-mobile/index.module.css +35 -0
  178. package/moderncssm/components/image-preview/Component.js +64 -26
  179. package/moderncssm/components/image-preview/index.module.css +30 -13
  180. package/moderncssm/components/image-preview/paths.d.ts +2 -0
  181. package/moderncssm/components/image-preview/paths.js +2 -0
  182. package/moderncssm/components/image-viewer/component.js +30 -10
  183. package/moderncssm/components/image-viewer/index.module.css +29 -6
  184. package/moderncssm/components/image-viewer/slide.js +14 -4
  185. package/moderncssm/components/image-viewer/video/index.d.ts +10 -0
  186. package/moderncssm/components/image-viewer/video/index.js +115 -0
  187. package/moderncssm/components/image-viewer/video/index.module.css +36 -0
  188. package/moderncssm/components/index.d.ts +2 -0
  189. package/moderncssm/components/index.js +2 -0
  190. package/moderncssm/components/info-bar/Component.d.ts +4 -0
  191. package/moderncssm/components/info-bar/Component.js +36 -0
  192. package/moderncssm/components/info-bar/index.d.ts +1 -0
  193. package/moderncssm/components/info-bar/index.js +1 -0
  194. package/moderncssm/components/info-bar/index.module.css +23 -0
  195. package/moderncssm/components/navigation-bar/Component.js +2 -1
  196. package/moderncssm/components/navigation-bar/index.module.css +4 -0
  197. package/moderncssm/context.d.ts +7 -0
  198. package/moderncssm/context.js +7 -0
  199. package/moderncssm/index.d.ts +1 -1
  200. package/moderncssm/index.js +1 -3
  201. package/moderncssm/index.module.css +18 -2
  202. package/moderncssm/types.d.ts +7 -0
  203. package/moderncssm/utils/constants.d.ts +14 -1
  204. package/moderncssm/utils/constants.js +14 -1
  205. package/moderncssm/utils/index.js +2 -2
  206. package/moderncssm/utils/utils.d.ts +2 -1
  207. package/moderncssm/utils/utils.js +5 -4
  208. package/package.json +2 -1
  209. package/{slide-12155967.js → slide-07755478.js} +13 -3
  210. package/src/Component.tsx +48 -6
  211. package/src/components/buttons/index.module.css +21 -0
  212. package/src/components/{header/buttons.tsx → buttons/index.tsx} +77 -0
  213. package/src/components/header/Component.tsx +33 -10
  214. package/src/components/header/index.module.css +0 -20
  215. package/src/components/header-mobile/Component.tsx +57 -0
  216. package/src/components/header-mobile/index.module.css +35 -0
  217. package/src/components/header-mobile/index.ts +1 -0
  218. package/src/components/image-preview/Component.tsx +131 -28
  219. package/src/components/image-preview/index.module.css +28 -9
  220. package/src/components/image-preview/paths.ts +3 -0
  221. package/src/components/image-viewer/component.tsx +32 -11
  222. package/src/components/image-viewer/index.module.css +26 -3
  223. package/src/components/image-viewer/slide.tsx +30 -4
  224. package/src/components/image-viewer/video/index.module.css +36 -0
  225. package/src/components/image-viewer/video/index.tsx +159 -0
  226. package/src/components/index.ts +2 -0
  227. package/src/components/info-bar/Component.tsx +68 -0
  228. package/src/components/info-bar/index.module.css +23 -0
  229. package/src/components/info-bar/index.ts +1 -0
  230. package/src/components/navigation-bar/Component.tsx +2 -1
  231. package/src/components/navigation-bar/index.module.css +4 -0
  232. package/src/context.ts +14 -0
  233. package/src/index.module.css +18 -2
  234. package/src/index.ts +1 -1
  235. package/src/types.ts +15 -5
  236. package/src/utils/constants.ts +17 -0
  237. package/src/utils/utils.ts +5 -3
  238. package/types.d.ts +7 -0
  239. package/utils/constants.d.ts +14 -1
  240. package/utils/constants.js +19 -0
  241. package/utils/index.js +7 -0
  242. package/utils/utils.d.ts +2 -1
  243. package/utils/utils.js +5 -3
  244. package/buttons-ec113bb4.js +0 -37
  245. package/components/header/buttons.d.ts +0 -0
  246. package/components/header/buttons.js +0 -20
  247. package/cssm/components/header/buttons.js +0 -37
  248. package/esm/buttons-791da71e.js +0 -27
  249. package/esm/components/header/buttons.d.ts +0 -0
  250. package/esm/components/header/buttons.js +0 -9
  251. package/modern/buttons-1859cb8e.js +0 -20
  252. package/modern/components/header/buttons.d.ts +0 -0
  253. package/modern/components/header/buttons.js +0 -8
  254. package/moderncssm/components/header/buttons.d.ts +0 -11
  255. package/moderncssm/components/header/buttons.js +0 -18
  256. /package/esm/{slide-7d5a41d1.d.ts → slide-c469a906.d.ts} +0 -0
  257. /package/modern/{slide-c47386c3.d.ts → slide-83826e9d.d.ts} +0 -0
  258. /package/{slide-12155967.d.ts → slide-07755478.d.ts} +0 -0
package/src/Component.tsx CHANGED
@@ -1,9 +1,11 @@
1
1
  import React, { FC, useCallback, useEffect, useState } from 'react';
2
+ import cn from 'classnames';
2
3
  import SwiperCore from 'swiper';
3
4
 
4
5
  import { BaseModal } from '@alfalab/core-components-base-modal';
6
+ import { useMedia } from '@alfalab/hooks';
5
7
 
6
- import { Header, ImageViewer, NavigationBar } from './components';
8
+ import { Header, HeaderMobile, ImageViewer, InfoBar, NavigationBar } from './components';
7
9
  import { GalleryContext } from './context';
8
10
  import { GalleryImage, ImageMeta } from './types';
9
11
 
@@ -48,6 +50,11 @@ export type GalleryProps = {
48
50
  onSlideIndexChange?: (index: number) => void;
49
51
  };
50
52
 
53
+ const DEFAULT_FULL_SCREEN = false;
54
+ const DEFAULT_MUTED_VIDEO = true;
55
+ const DEFAULT_PLAYING_VIDEO = true;
56
+ const DEFAULT_HIDE_NAVIGATION = false;
57
+
51
58
  const Backdrop = () => null;
52
59
 
53
60
  export const Gallery: FC<GalleryProps> = ({
@@ -67,7 +74,22 @@ export const Gallery: FC<GalleryProps> = ({
67
74
 
68
75
  const [swiper, setSwiper] = useState<SwiperCore>();
69
76
  const [imagesMeta, setImagesMeta] = useState<ImageMeta[]>([]);
70
- const [fullScreen, setFullScreen] = useState(false);
77
+ const [fullScreen, setFullScreen] = useState<boolean>(DEFAULT_FULL_SCREEN);
78
+ const [mutedVideo, setMutedVideo] = useState<boolean>(DEFAULT_MUTED_VIDEO);
79
+ const [playingVideo, setPlayingVideo] = useState<boolean>(DEFAULT_PLAYING_VIDEO);
80
+ const [hideNavigation, setHideNavigation] = useState<boolean>(DEFAULT_HIDE_NAVIGATION);
81
+
82
+ const [view] = useMedia<'desktop' | 'mobile'>(
83
+ [
84
+ ['mobile', '(max-width: 1023px)'],
85
+ ['desktop', '(min-width: 1024px)'],
86
+ ],
87
+ 'desktop',
88
+ );
89
+
90
+ const isMobile = view === 'mobile';
91
+
92
+ const isCurrentVideo = !!imagesMeta[currentSlideIndex]?.player?.current;
71
93
 
72
94
  const slideTo = useCallback(
73
95
  (index: number) => {
@@ -75,6 +97,7 @@ export const Gallery: FC<GalleryProps> = ({
75
97
  setCurrentSlideIndex?.(index);
76
98
 
77
99
  if (swiper) {
100
+ setPlayingVideo(true);
78
101
  swiper.slideTo(index);
79
102
  }
80
103
  }
@@ -151,6 +174,11 @@ export const Gallery: FC<GalleryProps> = ({
151
174
  [fullScreen, open, slideNext, slidePrev],
152
175
  );
153
176
 
177
+ const onUnmount = useCallback(() => {
178
+ setPlayingVideo(DEFAULT_PLAYING_VIDEO);
179
+ setMutedVideo(DEFAULT_MUTED_VIDEO);
180
+ }, [setPlayingVideo]);
181
+
154
182
  useEffect(() => {
155
183
  if (!uncontrolled && !swiper?.destroyed) {
156
184
  swiper?.slideTo(currentSlideIndex);
@@ -171,6 +199,7 @@ export const Gallery: FC<GalleryProps> = ({
171
199
 
172
200
  // eslint-disable-next-line react/jsx-no-constructed-context-values
173
201
  const galleryContext: GalleryContext = {
202
+ view,
174
203
  singleSlide,
175
204
  currentSlideIndex,
176
205
  images,
@@ -178,6 +207,12 @@ export const Gallery: FC<GalleryProps> = ({
178
207
  fullScreen,
179
208
  initialSlide: uncontrolled ? initialSlide : currentSlideIndex,
180
209
  setFullScreen,
210
+ playingVideo,
211
+ setPlayingVideo,
212
+ mutedVideo,
213
+ setMutedVideo,
214
+ hideNavigation,
215
+ setHideNavigation,
181
216
  setImageMeta,
182
217
  slideNext,
183
218
  slidePrev,
@@ -197,13 +232,20 @@ export const Gallery: FC<GalleryProps> = ({
197
232
  className={styles.modal}
198
233
  onEscapeKeyDown={handleEscapeKeyDown}
199
234
  Backdrop={Backdrop}
235
+ onUnmount={onUnmount}
200
236
  >
201
237
  <div className={styles.container}>
202
- <Header />
203
-
238
+ {view === 'desktop' ? <Header /> : <HeaderMobile />}
204
239
  <ImageViewer />
205
-
206
- {showNavigationBar && <NavigationBar />}
240
+ <nav
241
+ className={cn({
242
+ [styles.navigationVideo]: isCurrentVideo && isMobile,
243
+ [styles.hideNavigation]: hideNavigation && isMobile,
244
+ })}
245
+ >
246
+ {showNavigationBar && <NavigationBar />}
247
+ {view === 'mobile' && <InfoBar />}
248
+ </nav>
207
249
  </div>
208
250
  </BaseModal>
209
251
  </GalleryContext.Provider>
@@ -0,0 +1,21 @@
1
+ @import '@alfalab/core-components-vars/src/index.css';
2
+
3
+ .buttons {
4
+ display: flex;
5
+ padding-left: var(--gap-32);
6
+
7
+ & path {
8
+ color: var(--color-static-neutral-translucent-1300-inverted);
9
+ }
10
+ }
11
+
12
+ /* TODO: применять static цвет через prop IconButton'а */
13
+ .iconButton {
14
+ &:hover path {
15
+ color: var(--color-static-neutral-100-hover);
16
+ }
17
+
18
+ &:active path {
19
+ color: var(--color-static-neutral-100-press);
20
+ }
21
+ }
@@ -1,11 +1,17 @@
1
1
  import React, { FC, MutableRefObject } from 'react';
2
+ import cn from 'classnames';
2
3
 
3
4
  import { IconButton, IconButtonProps } from '@alfalab/core-components-icon-button';
4
5
  import { TooltipDesktop } from '@alfalab/core-components-tooltip/desktop';
6
+ import { ArrowLeftMIcon } from '@alfalab/icons-glyph/ArrowLeftMIcon';
5
7
  import { ArrowsInwardMIcon } from '@alfalab/icons-glyph/ArrowsInwardMIcon';
6
8
  import { ArrowsOutwardMIcon } from '@alfalab/icons-glyph/ArrowsOutwardMIcon';
7
9
  import { CrossMIcon } from '@alfalab/icons-glyph/CrossMIcon';
10
+ import { PauseCompactMIcon } from '@alfalab/icons-glyph/PauseCompactMIcon';
11
+ import { PlayCompactMIcon } from '@alfalab/icons-glyph/PlayCompactMIcon';
8
12
  import { PointerDownMIcon } from '@alfalab/icons-glyph/PointerDownMIcon';
13
+ import { SoundCrossMIcon } from '@alfalab/icons-glyph/SoundCrossMIcon';
14
+ import { SoundMIcon } from '@alfalab/icons-glyph/SoundMIcon';
9
15
 
10
16
  import styles from './index.module.css';
11
17
 
@@ -32,6 +38,39 @@ export const Fullscreen: FC<Props> = ({ buttonRef, ...restProps }) => (
32
38
  </TooltipDesktop>
33
39
  );
34
40
 
41
+ export const BackArrow: FC<Props> = ({ buttonRef, ...restProps }) => (
42
+ <IconButton
43
+ {...restProps}
44
+ ref={buttonRef}
45
+ icon={ArrowLeftMIcon}
46
+ colors='inverted'
47
+ aria-label='Вернуться назад'
48
+ className={styles.iconButton}
49
+ />
50
+ );
51
+
52
+ export const Play: FC<Props> = ({ buttonRef, className, ...restProps }) => (
53
+ <IconButton
54
+ {...restProps}
55
+ ref={buttonRef}
56
+ icon={PlayCompactMIcon}
57
+ colors='inverted'
58
+ aria-label='Проиграть видео'
59
+ className={cn(styles.iconButton, className)}
60
+ />
61
+ );
62
+
63
+ export const Pause: FC<Props> = ({ buttonRef, className, ...restProps }) => (
64
+ <IconButton
65
+ {...restProps}
66
+ ref={buttonRef}
67
+ icon={PauseCompactMIcon}
68
+ colors='inverted'
69
+ aria-label='Поставить паузу на видео'
70
+ className={cn(styles.iconButton, className)}
71
+ />
72
+ );
73
+
35
74
  export const ExitFullscreen: FC<Props> = ({ buttonRef, ...restProps }) => (
36
75
  <TooltipDesktop
37
76
  trigger='hover'
@@ -50,6 +89,44 @@ export const ExitFullscreen: FC<Props> = ({ buttonRef, ...restProps }) => (
50
89
  </TooltipDesktop>
51
90
  );
52
91
 
92
+ export const MuteVideo: FC<Props> = ({ buttonRef, className, ...restProps }) => (
93
+ <TooltipDesktop
94
+ trigger='hover'
95
+ position='bottom'
96
+ content='Выключить звук'
97
+ fallbackPlacements={['bottom-end']}
98
+ targetClassName={className}
99
+ >
100
+ <IconButton
101
+ {...restProps}
102
+ ref={buttonRef}
103
+ icon={SoundMIcon}
104
+ colors='inverted'
105
+ aria-label='Выключить звук'
106
+ className={styles.iconButton}
107
+ />
108
+ </TooltipDesktop>
109
+ );
110
+
111
+ export const UnmuteVideo: FC<Props> = ({ buttonRef, className, ...restProps }) => (
112
+ <TooltipDesktop
113
+ trigger='hover'
114
+ position='bottom'
115
+ content='Включить звук'
116
+ fallbackPlacements={['bottom-end']}
117
+ targetClassName={className}
118
+ >
119
+ <IconButton
120
+ {...restProps}
121
+ ref={buttonRef}
122
+ icon={SoundCrossMIcon}
123
+ colors='inverted'
124
+ aria-label='Включить звук'
125
+ className={styles.iconButton}
126
+ />
127
+ </TooltipDesktop>
128
+ );
129
+
53
130
  export const Download: FC<Props> = (props) => (
54
131
  <TooltipDesktop
55
132
  trigger='hover'
@@ -1,11 +1,10 @@
1
1
  import React, { FC, useContext, useEffect, useRef } from 'react';
2
2
 
3
3
  import { GalleryContext } from '../../context';
4
- import { isSmallImage, TestIds } from '../../utils';
4
+ import { GALLERY_EVENTS, isSmallImage, isVideo, TestIds } from '../../utils';
5
+ import * as Buttons from '../buttons';
5
6
  import { HeaderInfoBlock } from '../header-info-block';
6
7
 
7
- import * as Buttons from './buttons';
8
-
9
8
  import styles from './index.module.css';
10
9
 
11
10
  export const Header: FC = () => {
@@ -18,10 +17,32 @@ export const Header: FC = () => {
18
17
  getCurrentImage,
19
18
  setFullScreen,
20
19
  onClose,
20
+ mutedVideo,
21
+ setMutedVideo,
21
22
  } = useContext(GalleryContext);
22
23
 
24
+ const currentImage = getCurrentImage();
25
+ const meta = getCurrentImageMeta();
26
+
23
27
  const toggleFullScreenButton = useRef<HTMLButtonElement>(null);
24
28
 
29
+ const onMuteButtonClick = () => {
30
+ if (mutedVideo) {
31
+ const customEvent = new CustomEvent(GALLERY_EVENTS.ON_UNMUTE, {
32
+ detail: { player: meta?.player?.current },
33
+ });
34
+
35
+ dispatchEvent(customEvent);
36
+ } else {
37
+ const customEvent = new CustomEvent(GALLERY_EVENTS.ON_MUTE, {
38
+ detail: { player: meta?.player?.current },
39
+ });
40
+
41
+ dispatchEvent(customEvent);
42
+ }
43
+ setMutedVideo(!mutedVideo);
44
+ };
45
+
25
46
  const closeFullScreen = () => {
26
47
  setFullScreen(false);
27
48
  };
@@ -36,16 +57,10 @@ export const Header: FC = () => {
36
57
  }
37
58
  }, [fullScreen]);
38
59
 
39
- const currentImage = getCurrentImage();
40
-
41
60
  const canDownload = currentImage?.canDownload ?? true;
42
61
  const filename = currentImage?.name || '';
43
62
  const description =
44
- singleSlide || !images.length
45
- ? ''
46
- : `Изображение ${currentSlideIndex + 1} из ${images.length}`;
47
-
48
- const meta = getCurrentImageMeta();
63
+ singleSlide || !images.length ? '' : `${currentSlideIndex + 1} из ${images.length}`;
49
64
 
50
65
  const showFullScreenButton = !isSmallImage(meta) && !meta?.broken;
51
66
  const showDownloadButton = !meta?.broken && canDownload;
@@ -65,11 +80,19 @@ export const Header: FC = () => {
65
80
  />
66
81
  );
67
82
 
83
+ const renderToggleMuteVideo = () =>
84
+ mutedVideo ? (
85
+ <Buttons.UnmuteVideo onClick={onMuteButtonClick} dataTestId={TestIds.UNMUTE_BUTTON} />
86
+ ) : (
87
+ <Buttons.MuteVideo onClick={onMuteButtonClick} dataTestId={TestIds.MUTE_BUTTON} />
88
+ );
89
+
68
90
  return (
69
91
  <div className={styles.header}>
70
92
  <HeaderInfoBlock filename={filename} description={description} />
71
93
 
72
94
  <div className={styles.buttons}>
95
+ {isVideo(currentImage?.src) && renderToggleMuteVideo()}
73
96
  {showFullScreenButton && renderToggleFullScreenButton()}
74
97
 
75
98
  {showDownloadButton && (
@@ -8,23 +8,3 @@
8
8
  padding: var(--gap-16) var(--gap-24);
9
9
  box-sizing: border-box;
10
10
  }
11
-
12
- .buttons {
13
- display: flex;
14
- padding-left: var(--gap-32);
15
-
16
- & path {
17
- color: var(--color-static-neutral-translucent-1300-inverted);
18
- }
19
- }
20
-
21
- /* TODO: применять static цвет через prop IconButton'а */
22
- .iconButton {
23
- &:hover path {
24
- color: var(--color-static-neutral-100-hover);
25
- }
26
-
27
- &:active path {
28
- color: var(--color-static-neutral-100-press);
29
- }
30
- }
@@ -0,0 +1,57 @@
1
+ import React, { useContext } from 'react';
2
+ import cn from 'classnames';
3
+
4
+ import { Typography } from '@alfalab/core-components-typography';
5
+
6
+ import { GalleryContext } from '../../context';
7
+ import { isVideo, TestIds } from '../../utils';
8
+ import * as Buttons from '../buttons';
9
+
10
+ import styles from './index.module.css';
11
+
12
+ export const HeaderMobile = () => {
13
+ const {
14
+ onClose,
15
+ images,
16
+ currentSlideIndex,
17
+ getCurrentImage,
18
+ getCurrentImageMeta,
19
+ hideNavigation,
20
+ } = useContext(GalleryContext);
21
+
22
+ const currentImage = getCurrentImage();
23
+ const meta = getCurrentImageMeta();
24
+
25
+ const description = images.length > 1 && `${currentSlideIndex + 1} из ${images.length}`;
26
+
27
+ const canDownload = currentImage?.canDownload ?? true;
28
+ const showDownloadButton = !meta?.broken && canDownload;
29
+
30
+ return (
31
+ <div
32
+ className={cn(styles.headerMobile, {
33
+ [styles.video]: isVideo(currentImage?.src),
34
+ [styles.hide]: hideNavigation,
35
+ })}
36
+ >
37
+ <Buttons.BackArrow onClick={onClose} />
38
+ <Typography.Text
39
+ className={styles.description}
40
+ tag='div'
41
+ view='component-primary'
42
+ color='static-primary-light'
43
+ >
44
+ {description}
45
+ </Typography.Text>
46
+ <div className={styles.rightButtons}>
47
+ {showDownloadButton && (
48
+ <Buttons.Download
49
+ href={currentImage?.src}
50
+ download={currentImage?.name}
51
+ dataTestId={TestIds.DOWNLOAD_BUTTON}
52
+ />
53
+ )}
54
+ </div>
55
+ </div>
56
+ );
57
+ };
@@ -0,0 +1,35 @@
1
+ @import '@alfalab/core-components-vars/src/index.css';
2
+
3
+ .headerMobile {
4
+ position: relative;
5
+ height: 72px;
6
+ z-index: 3;
7
+ padding: var(--gap-12) var(--gap-8);
8
+ display: flex;
9
+ justify-content: space-between;
10
+ align-items: center;
11
+
12
+ &.video {
13
+ position: absolute;
14
+ width: 100%;
15
+ padding: 0;
16
+ background-color: var(--color-static-neutral-0-inverted);
17
+ transition: transform 0.3s ease-in-out;
18
+ }
19
+ }
20
+
21
+ .hide {
22
+ transform: translateY(-96px);
23
+ }
24
+
25
+ .leftButton,
26
+ .rightButtons {
27
+ display: flex;
28
+ align-items: center;
29
+ }
30
+
31
+ .description {
32
+ position: absolute;
33
+ left: 50%;
34
+ transform: translateX(-50%);
35
+ }
@@ -0,0 +1 @@
1
+ export { HeaderMobile } from './Component';
@@ -1,11 +1,19 @@
1
- import React, { FC, KeyboardEventHandler, useContext, useRef } from 'react';
1
+ import React, { FC, KeyboardEventHandler, useContext, useEffect, useRef } from 'react';
2
2
  import cn from 'classnames';
3
3
 
4
- import { getImageAlt } from '@alfalab/core-components-gallery';
5
4
  import { useFocus } from '@alfalab/hooks';
6
5
 
7
6
  import { GalleryContext } from '../../context';
8
7
  import { GalleryImage } from '../../types';
8
+ import {
9
+ getImageAlt,
10
+ isVideo,
11
+ PREVIEW_HEIGHT_DESKTOP,
12
+ PREVIEW_HEIGHT_MOBILE,
13
+ PREVIEW_VIDEO_MULTIPLIER,
14
+ PREVIEW_WIDTH_DESKTOP,
15
+ PREVIEW_WIDTH_MOBILE,
16
+ } from '../../utils';
9
17
 
10
18
  import { NoImagePaths } from './paths';
11
19
 
@@ -20,9 +28,44 @@ type Props = {
20
28
  };
21
29
 
22
30
  export const ImagePreview: FC<Props> = ({ image, active = false, index, onSelect, className }) => {
23
- const { imagesMeta } = useContext(GalleryContext);
31
+ const { imagesMeta, view } = useContext(GalleryContext);
32
+
33
+ const isMobile = view === 'mobile';
34
+
35
+ const previewWidth = isMobile ? PREVIEW_WIDTH_MOBILE : PREVIEW_WIDTH_DESKTOP;
36
+ const previewHeight = isMobile ? PREVIEW_HEIGHT_MOBILE : PREVIEW_HEIGHT_DESKTOP;
24
37
 
25
38
  const ref = useRef<HTMLDivElement>(null);
39
+ const canvasRef = useRef<HTMLCanvasElement>(null);
40
+
41
+ useEffect(() => {
42
+ const canvas = canvasRef.current;
43
+ const video = imagesMeta[index]?.player?.current;
44
+ const context = canvas?.getContext('2d');
45
+ const drawPreview = () => {
46
+ if (video) {
47
+ context?.drawImage(
48
+ video,
49
+ 0,
50
+ 0,
51
+ video.videoWidth / PREVIEW_VIDEO_MULTIPLIER,
52
+ video.videoHeight / PREVIEW_VIDEO_MULTIPLIER,
53
+ );
54
+ }
55
+ };
56
+
57
+ drawPreview();
58
+
59
+ if (isVideo(image.src)) {
60
+ video?.addEventListener('canplay', drawPreview);
61
+ }
62
+
63
+ return () => {
64
+ if (isVideo(image.src)) {
65
+ video?.removeEventListener('canplay', drawPreview);
66
+ }
67
+ };
68
+ }, [image.src, imagesMeta, index]);
26
69
 
27
70
  const handleClick = () => {
28
71
  onSelect(index);
@@ -40,50 +83,110 @@ export const ImagePreview: FC<Props> = ({ image, active = false, index, onSelect
40
83
 
41
84
  const isBroken = Boolean(meta?.broken);
42
85
 
43
- return (
44
- <div
45
- className={cn(
46
- styles.component,
47
- { [styles.active]: active, [styles.focused]: focused },
48
- className,
49
- )}
50
- onClick={handleClick}
51
- role='button'
52
- onKeyDown={handleKeyDown}
53
- tabIndex={0}
54
- ref={ref}
55
- aria-label={`Перейти к изображению ${index + 1}`}
56
- >
57
- {isBroken ? (
58
- <div className={cn(styles.preview, styles.brokenImageWrapper)}>
59
- <div className={styles.brokenIcon}>
86
+ const renderPreview = () => {
87
+ if (isBroken) {
88
+ return (
89
+ <div
90
+ className={cn(
91
+ styles.preview,
92
+ { [styles.mobile]: isMobile, [styles.active]: active },
93
+ styles.brokenImageWrapper,
94
+ )}
95
+ >
96
+ <div className={cn(styles.brokenIcon)}>
60
97
  <svg
61
98
  xmlns='http://www.w3.org/2000/svg'
62
- width='40'
63
- height='40'
64
- viewBox='0 0 40 40'
99
+ width={previewWidth}
100
+ height={previewHeight}
101
+ viewBox={`${isMobile ? -6 : 0} ${
102
+ isMobile ? -12 : 0
103
+ } ${previewWidth} ${previewHeight}`}
65
104
  fill='none'
66
105
  >
67
- <rect width='40' height='40' fill='none' />
106
+ <rect fill='none' />
68
107
  <path
69
108
  fillRule='evenodd'
70
109
  clipRule='evenodd'
71
- d={NoImagePaths.baseImage}
110
+ d={isMobile ? NoImagePaths.mobileImage : NoImagePaths.baseImage}
111
+ fill='#89898A'
112
+ />
113
+ <path
114
+ d={
115
+ isMobile
116
+ ? NoImagePaths.mobileTriangle
117
+ : NoImagePaths.triangleImage
118
+ }
72
119
  fill='#89898A'
73
120
  />
74
- <path d={NoImagePaths.triangleImage} fill='#89898A' />
75
121
  </svg>
76
122
  </div>
77
123
  </div>
78
- ) : (
124
+ );
125
+ }
126
+
127
+ if (image.previewSrc) {
128
+ return (
129
+ <div
130
+ className={cn(styles.preview, styles.image, {
131
+ [styles.loading]: !meta,
132
+ [styles.mobile]: isMobile,
133
+ })}
134
+ >
135
+ <img src={image.previewSrc} alt={getImageAlt(image, index)} />
136
+ </div>
137
+ );
138
+ }
139
+
140
+ if (isVideo(image.src)) {
141
+ return (
79
142
  <div
80
143
  className={cn(styles.preview, styles.image, {
81
144
  [styles.loading]: !meta,
145
+ [styles.mobile]: isMobile,
82
146
  })}
83
147
  >
84
- <img src={image.src} alt={getImageAlt(image, index)} />
148
+ <canvas
149
+ className={cn(styles.canvasPreview, { [styles.mobile]: isMobile })}
150
+ data-testid='canvas'
151
+ width={previewWidth}
152
+ height={previewHeight}
153
+ ref={canvasRef}
154
+ />
85
155
  </div>
156
+ );
157
+ }
158
+
159
+ return (
160
+ <div
161
+ className={cn(styles.preview, styles.image, {
162
+ [styles.loading]: !meta,
163
+ [styles.mobile]: isMobile,
164
+ })}
165
+ >
166
+ <img src={image.src} alt={getImageAlt(image, index)} />
167
+ </div>
168
+ );
169
+ };
170
+
171
+ return (
172
+ <div
173
+ className={cn(
174
+ styles.component,
175
+ {
176
+ [styles.active]: active,
177
+ [styles.focused]: focused,
178
+ [styles.mobile]: isMobile,
179
+ },
180
+ className,
86
181
  )}
182
+ onClick={handleClick}
183
+ role='button'
184
+ onKeyDown={handleKeyDown}
185
+ tabIndex={0}
186
+ ref={ref}
187
+ aria-label={`Перейти к ${index + 1} элементу`}
188
+ >
189
+ {renderPreview()}
87
190
  </div>
88
191
  );
89
192
  };