@telefonica/mistica 16.58.0-beta.2 → 16.59.0-beta.1

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 (539) hide show
  1. package/css/mistica.css +1 -1
  2. package/dist/accordion.css-mistica.js +6 -6
  3. package/dist/align.css-mistica.js +1 -1
  4. package/dist/autocomplete.css-mistica.js +1 -1
  5. package/dist/avatar.css-mistica.js +1 -1
  6. package/dist/badge.css-mistica.js +1 -1
  7. package/dist/box.css-mistica.js +13 -13
  8. package/dist/boxed.css-mistica.js +24 -24
  9. package/dist/button-group.css-mistica.js +1 -1
  10. package/dist/button-layout.css-mistica.js +14 -14
  11. package/dist/button.css-mistica.js +30 -30
  12. package/dist/callout.css-mistica.js +11 -11
  13. package/dist/card-internal.css-mistica.js +15 -15
  14. package/dist/carousel.css-mistica.js +8 -8
  15. package/dist/checkbox.css-mistica.js +11 -11
  16. package/dist/chip.css-mistica.js +15 -15
  17. package/dist/circle.css-mistica.js +1 -1
  18. package/dist/community/advanced-data-card.css-mistica.js +6 -6
  19. package/dist/community/blocks.css-mistica.js +1 -1
  20. package/dist/community/example-component.css-mistica.js +1 -1
  21. package/dist/counter.css-mistica.js +1 -1
  22. package/dist/cover-hero.css-mistica.js +2 -2
  23. package/dist/credit-card-number-field.css-mistica.js +3 -3
  24. package/dist/date-field.css-mistica.js +1 -1
  25. package/dist/date-time-picker.css-mistica.js +1 -1
  26. package/dist/dialog.css-mistica.js +4 -4
  27. package/dist/divider.css-mistica.js +5 -5
  28. package/dist/double-field.css-mistica.js +2 -2
  29. package/dist/drawer.css-mistica.js +1 -1
  30. package/dist/empty-state-card.css-mistica.js +1 -1
  31. package/dist/empty-state.css-mistica.js +5 -5
  32. package/dist/fade-in.css-mistica.js +1 -1
  33. package/dist/feedback.css-mistica.js +1 -1
  34. package/dist/file-upload.css-mistica.js +7 -7
  35. package/dist/fixed-footer-layout.css-mistica.js +2 -2
  36. package/dist/form.css-mistica.js +1 -1
  37. package/dist/grid-layout.css-mistica.js +3 -3
  38. package/dist/grid.css-mistica.js +120 -120
  39. package/dist/header.css-mistica.js +1 -1
  40. package/dist/hero.css-mistica.js +2 -2
  41. package/dist/horizontal-scroll.css-mistica.js +1 -1
  42. package/dist/icon-button.css-mistica.js +53 -53
  43. package/dist/icons/icon-chevron.css-mistica.js +2 -2
  44. package/dist/icons/icon-error.css-mistica.js +1 -1
  45. package/dist/image.css-mistica.js +2 -2
  46. package/dist/image.js +31 -30
  47. package/dist/inline.css-mistica.js +9 -9
  48. package/dist/list.css-mistica.js +1 -1
  49. package/dist/loading-bar.css-mistica.js +1 -1
  50. package/dist/loading-screen.css-mistica.js +4 -4
  51. package/dist/logo.css-mistica.js +5 -5
  52. package/dist/menu.css-mistica.js +13 -13
  53. package/dist/mosaic.css-mistica.js +1 -1
  54. package/dist/navigation-bar.css-mistica.js +18 -18
  55. package/dist/navigation-breadcrumbs.css-mistica.js +1 -1
  56. package/dist/package-version.js +1 -1
  57. package/dist/pin-field.css-mistica.js +1 -1
  58. package/dist/popover.css-mistica.js +1 -1
  59. package/dist/progress-bar.css-mistica.js +6 -6
  60. package/dist/radio-button.css-mistica.js +19 -19
  61. package/dist/rating.css-mistica.js +2 -2
  62. package/dist/responsive-layout.css-mistica.js +6 -6
  63. package/dist/screen-reader-only.css-mistica.js +1 -1
  64. package/dist/select.css-mistica.js +15 -15
  65. package/dist/sheet-action-row.css-mistica.js +1 -1
  66. package/dist/sheet-common.css-mistica.js +1 -1
  67. package/dist/sheet-info.css-mistica.js +1 -1
  68. package/dist/skeletons.css-mistica.js +6 -6
  69. package/dist/skins/skin-contract.css-mistica.js +684 -684
  70. package/dist/skip-link.css-mistica.js +1 -1
  71. package/dist/slider.css-mistica.js +18 -18
  72. package/dist/snackbar.css-mistica.js +4 -4
  73. package/dist/spinner.css-mistica.js +1 -1
  74. package/dist/square.css-mistica.js +1 -1
  75. package/dist/stack.css-mistica.js +5 -5
  76. package/dist/stacking-group.css-mistica.js +1 -1
  77. package/dist/stepper.css-mistica.js +2 -2
  78. package/dist/switch-component.css-mistica.js +35 -35
  79. package/dist/table.css-mistica.js +9 -9
  80. package/dist/tabs.css-mistica.js +17 -17
  81. package/dist/tag.css-mistica.js +1 -1
  82. package/dist/text-field-base.css-mistica.js +15 -15
  83. package/dist/text-field-components.css-mistica.js +3 -3
  84. package/dist/text-link.css-mistica.js +6 -6
  85. package/dist/text.css-mistica.js +6 -6
  86. package/dist/theme-context.css-mistica.js +1 -1
  87. package/dist/timeline.css-mistica.js +9 -9
  88. package/dist/timer.css-mistica.js +6 -6
  89. package/dist/tooltip.css-mistica.js +1 -1
  90. package/dist/touchable.css-mistica.js +1 -1
  91. package/dist/utils/aspect-ratio-support.css-mistica.js +2 -2
  92. package/dist/video.css-mistica.js +1 -1
  93. package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  94. package/dist-es/accordion.css-mistica.js +6 -6
  95. package/dist-es/align.css-mistica.js +1 -1
  96. package/dist-es/autocomplete.css-mistica.js +1 -1
  97. package/dist-es/avatar.css-mistica.js +1 -1
  98. package/dist-es/badge.css-mistica.js +1 -1
  99. package/dist-es/box.css-mistica.js +13 -13
  100. package/dist-es/boxed.css-mistica.js +23 -23
  101. package/dist-es/button-group.css-mistica.js +1 -1
  102. package/dist-es/button-layout.css-mistica.js +14 -14
  103. package/dist-es/button.css-mistica.js +30 -30
  104. package/dist-es/callout.css-mistica.js +11 -11
  105. package/dist-es/card-internal.css-mistica.js +15 -15
  106. package/dist-es/carousel.css-mistica.js +8 -8
  107. package/dist-es/checkbox.css-mistica.js +11 -11
  108. package/dist-es/chip.css-mistica.js +15 -15
  109. package/dist-es/circle.css-mistica.js +1 -1
  110. package/dist-es/community/advanced-data-card.css-mistica.js +6 -6
  111. package/dist-es/community/blocks.css-mistica.js +1 -1
  112. package/dist-es/community/example-component.css-mistica.js +1 -1
  113. package/dist-es/counter.css-mistica.js +1 -1
  114. package/dist-es/cover-hero.css-mistica.js +2 -2
  115. package/dist-es/credit-card-number-field.css-mistica.js +3 -3
  116. package/dist-es/date-field.css-mistica.js +1 -1
  117. package/dist-es/date-time-picker.css-mistica.js +1 -1
  118. package/dist-es/dialog.css-mistica.js +4 -4
  119. package/dist-es/divider.css-mistica.js +5 -5
  120. package/dist-es/double-field.css-mistica.js +2 -2
  121. package/dist-es/drawer.css-mistica.js +1 -1
  122. package/dist-es/empty-state-card.css-mistica.js +1 -1
  123. package/dist-es/empty-state.css-mistica.js +5 -5
  124. package/dist-es/fade-in.css-mistica.js +1 -1
  125. package/dist-es/feedback.css-mistica.js +1 -1
  126. package/dist-es/file-upload.css-mistica.js +7 -7
  127. package/dist-es/fixed-footer-layout.css-mistica.js +2 -2
  128. package/dist-es/form.css-mistica.js +1 -1
  129. package/dist-es/grid-layout.css-mistica.js +3 -3
  130. package/dist-es/grid.css-mistica.js +120 -120
  131. package/dist-es/header.css-mistica.js +1 -1
  132. package/dist-es/hero.css-mistica.js +2 -2
  133. package/dist-es/horizontal-scroll.css-mistica.js +1 -1
  134. package/dist-es/icon-button.css-mistica.js +53 -53
  135. package/dist-es/icons/icon-chevron.css-mistica.js +2 -2
  136. package/dist-es/icons/icon-error.css-mistica.js +1 -1
  137. package/dist-es/image.css-mistica.js +2 -2
  138. package/dist-es/image.js +33 -32
  139. package/dist-es/inline.css-mistica.js +9 -9
  140. package/dist-es/list.css-mistica.js +1 -1
  141. package/dist-es/loading-bar.css-mistica.js +1 -1
  142. package/dist-es/loading-screen.css-mistica.js +4 -4
  143. package/dist-es/logo.css-mistica.js +5 -5
  144. package/dist-es/menu.css-mistica.js +13 -13
  145. package/dist-es/mosaic.css-mistica.js +1 -1
  146. package/dist-es/navigation-bar.css-mistica.js +18 -18
  147. package/dist-es/navigation-breadcrumbs.css-mistica.js +1 -1
  148. package/dist-es/package-version.js +1 -1
  149. package/dist-es/pin-field.css-mistica.js +1 -1
  150. package/dist-es/popover.css-mistica.js +1 -1
  151. package/dist-es/progress-bar.css-mistica.js +6 -6
  152. package/dist-es/radio-button.css-mistica.js +19 -19
  153. package/dist-es/rating.css-mistica.js +2 -2
  154. package/dist-es/responsive-layout.css-mistica.js +6 -6
  155. package/dist-es/screen-reader-only.css-mistica.js +1 -1
  156. package/dist-es/select.css-mistica.js +15 -15
  157. package/dist-es/sheet-action-row.css-mistica.js +1 -1
  158. package/dist-es/sheet-common.css-mistica.js +1 -1
  159. package/dist-es/sheet-info.css-mistica.js +1 -1
  160. package/dist-es/skeletons.css-mistica.js +6 -6
  161. package/dist-es/skins/skin-contract.css-mistica.js +684 -684
  162. package/dist-es/skip-link.css-mistica.js +1 -1
  163. package/dist-es/slider.css-mistica.js +18 -18
  164. package/dist-es/snackbar.css-mistica.js +4 -4
  165. package/dist-es/spinner.css-mistica.js +1 -1
  166. package/dist-es/square.css-mistica.js +1 -1
  167. package/dist-es/stack.css-mistica.js +5 -5
  168. package/dist-es/stacking-group.css-mistica.js +1 -1
  169. package/dist-es/stepper.css-mistica.js +2 -2
  170. package/dist-es/style.css +1 -1
  171. package/dist-es/switch-component.css-mistica.js +35 -35
  172. package/dist-es/table.css-mistica.js +9 -9
  173. package/dist-es/tabs.css-mistica.js +17 -17
  174. package/dist-es/tag.css-mistica.js +1 -1
  175. package/dist-es/text-field-base.css-mistica.js +15 -15
  176. package/dist-es/text-field-components.css-mistica.js +3 -3
  177. package/dist-es/text-link.css-mistica.js +6 -6
  178. package/dist-es/text.css-mistica.js +6 -6
  179. package/dist-es/theme-context.css-mistica.js +1 -1
  180. package/dist-es/timeline.css-mistica.js +9 -9
  181. package/dist-es/timer.css-mistica.js +6 -6
  182. package/dist-es/tooltip.css-mistica.js +1 -1
  183. package/dist-es/touchable.css-mistica.js +1 -1
  184. package/dist-es/utils/aspect-ratio-support.css-mistica.js +2 -2
  185. package/dist-es/video.css-mistica.js +1 -1
  186. package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  187. package/doc/llms.md +35 -11
  188. package/doc/patterns.md +22 -20
  189. package/package.json +13 -1
  190. package/src/accordion.css.ts +121 -0
  191. package/src/accordion.tsx +366 -0
  192. package/src/align.css.ts +7 -0
  193. package/src/align.tsx +32 -0
  194. package/src/autocomplete.css.ts +62 -0
  195. package/src/autocomplete.tsx +239 -0
  196. package/src/avatar.css.ts +14 -0
  197. package/src/avatar.tsx +120 -0
  198. package/src/badge.css.ts +51 -0
  199. package/src/badge.tsx +79 -0
  200. package/src/box.css.ts +51 -0
  201. package/src/box.tsx +114 -0
  202. package/src/boxed.css.ts +132 -0
  203. package/src/boxed.tsx +153 -0
  204. package/src/button-fixed-footer-layout.tsx +62 -0
  205. package/src/button-group.css.ts +75 -0
  206. package/src/button-group.tsx +91 -0
  207. package/src/button-layout.css.ts +162 -0
  208. package/src/button-layout.tsx +91 -0
  209. package/src/button.css.ts +758 -0
  210. package/src/button.tsx +632 -0
  211. package/src/callout.css.ts +50 -0
  212. package/src/callout.tsx +147 -0
  213. package/src/card-cover.tsx +242 -0
  214. package/src/card-data.tsx +152 -0
  215. package/src/card-internal.css.ts +271 -0
  216. package/src/card-internal.tsx +1724 -0
  217. package/src/card-media.tsx +157 -0
  218. package/src/card-naked.tsx +63 -0
  219. package/src/carousel.css.ts +522 -0
  220. package/src/carousel.tsx +1300 -0
  221. package/src/checkbox.css.ts +94 -0
  222. package/src/checkbox.tsx +192 -0
  223. package/src/chip.css.ts +204 -0
  224. package/src/chip.tsx +191 -0
  225. package/src/circle.css.ts +14 -0
  226. package/src/circle.tsx +52 -0
  227. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-button-and-link-footer-image-false-1-snap.png +0 -0
  228. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-button-and-link-footer-image-true-1-snap.png +0 -0
  229. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-button-footer-image-false-1-snap.png +0 -0
  230. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-button-footer-image-true-1-snap.png +0 -0
  231. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-link-footer-image-false-1-snap.png +0 -0
  232. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-link-footer-image-true-1-snap.png +0 -0
  233. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-none-footer-image-false-1-snap.png +0 -0
  234. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-actions-none-footer-image-true-1-snap.png +0 -0
  235. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-extras-0-1-snap.png +0 -0
  236. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-extras-1-1-snap.png +0 -0
  237. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-extras-3-1-snap.png +0 -0
  238. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-extras-3-no-divider-1-snap.png +0 -0
  239. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-inside-carousel-1-snap.png +0 -0
  240. package/src/community/__screenshot_tests__/__image_snapshots__/advanced-data-card-screenshot-test-tsx-advanced-data-card-without-stacking-group-with-top-actions-and-too-long-title-1-snap.png +0 -0
  241. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-highlighted-value-block-1-snap.png +0 -0
  242. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-information-block-1-snap.png +0 -0
  243. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-progress-block-1-snap.png +0 -0
  244. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-progress-block-2-snap.png +0 -0
  245. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-row-block-1-snap.png +0 -0
  246. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-simple-block-1-snap.png +0 -0
  247. package/src/community/__screenshot_tests__/__image_snapshots__/blocks-screenshot-test-tsx-blocks-value-block-1-snap.png +0 -0
  248. package/src/community/__screenshot_tests__/advanced-data-card-screenshot-test.tsx +84 -0
  249. package/src/community/__screenshot_tests__/blocks-screenshot-test.tsx +72 -0
  250. package/src/community/__stories__/advanced-data-card-carousel-story.tsx +66 -0
  251. package/src/community/__stories__/advanced-data-card-story.tsx +158 -0
  252. package/src/community/__stories__/blocks-story.tsx +272 -0
  253. package/src/community/__stories__/example-component-story.tsx +15 -0
  254. package/src/community/__stories__/index-story.tsx +154 -0
  255. package/src/community/__type_tests__/advanced-data-card-type-test.tsx +40 -0
  256. package/src/community/advanced-data-card.css.ts +271 -0
  257. package/src/community/advanced-data-card.tsx +431 -0
  258. package/src/community/blocks.css.ts +12 -0
  259. package/src/community/blocks.tsx +290 -0
  260. package/src/community/example-component.css.ts +7 -0
  261. package/src/community/example-component.tsx +17 -0
  262. package/src/community/index.tsx +10 -0
  263. package/src/counter.css.ts +150 -0
  264. package/src/counter.tsx +215 -0
  265. package/src/cover-hero-media.tsx +39 -0
  266. package/src/cover-hero.css.ts +133 -0
  267. package/src/cover-hero.tsx +262 -0
  268. package/src/credit-card-expiration-field.tsx +187 -0
  269. package/src/credit-card-fields.tsx +56 -0
  270. package/src/credit-card-number-field.css.ts +47 -0
  271. package/src/credit-card-number-field.tsx +245 -0
  272. package/src/cvv-field.tsx +169 -0
  273. package/src/date-field.css.ts +14 -0
  274. package/src/date-field.tsx +130 -0
  275. package/src/date-time-field.tsx +141 -0
  276. package/src/date-time-picker.css.ts +126 -0
  277. package/src/date-time-picker.tsx +188 -0
  278. package/src/decimal-field.tsx +160 -0
  279. package/src/desktop-container-type-context.tsx +15 -0
  280. package/src/dialog-context.tsx +81 -0
  281. package/src/dialog.css.ts +155 -0
  282. package/src/dialog.tsx +423 -0
  283. package/src/divider.css.ts +10 -0
  284. package/src/divider.tsx +11 -0
  285. package/src/double-field.css.ts +33 -0
  286. package/src/double-field.tsx +71 -0
  287. package/src/drawer.css.ts +123 -0
  288. package/src/drawer.tsx +304 -0
  289. package/src/email-field.tsx +76 -0
  290. package/src/empty-state-card.css.ts +40 -0
  291. package/src/empty-state-card.tsx +92 -0
  292. package/src/empty-state.css.ts +119 -0
  293. package/src/empty-state.tsx +141 -0
  294. package/src/fade-in.css.ts +12 -0
  295. package/src/fade-in.tsx +40 -0
  296. package/src/feedback.css.ts +119 -0
  297. package/src/feedback.tsx +432 -0
  298. package/src/file-upload.css.ts +156 -0
  299. package/src/file-upload.tsx +612 -0
  300. package/src/fixed-footer-layout.css.ts +96 -0
  301. package/src/fixed-footer-layout.tsx +215 -0
  302. package/src/fixed-to-top.tsx +21 -0
  303. package/src/focus-trap.tsx +17 -0
  304. package/src/form-context.tsx +198 -0
  305. package/src/form.css.ts +5 -0
  306. package/src/form.tsx +287 -0
  307. package/src/grid-layout.css.ts +68 -0
  308. package/src/grid-layout.tsx +201 -0
  309. package/src/grid.css.ts +203 -0
  310. package/src/grid.tsx +241 -0
  311. package/src/header.css.ts +30 -0
  312. package/src/header.tsx +319 -0
  313. package/src/hero.css.ts +71 -0
  314. package/src/hero.tsx +318 -0
  315. package/src/hooks.tsx +313 -0
  316. package/src/horizontal-scroll.css.ts +43 -0
  317. package/src/horizontal-scroll.tsx +18 -0
  318. package/src/iban-field.tsx +218 -0
  319. package/src/icon-button.css.ts +561 -0
  320. package/src/icon-button.tsx +221 -0
  321. package/src/icons/__stories__/mistica-icons-story.tsx +192 -0
  322. package/src/icons/icon-amex.tsx +40 -0
  323. package/src/icons/icon-chevron.css.ts +23 -0
  324. package/src/icons/icon-chevron.tsx +150 -0
  325. package/src/icons/icon-cvv-amex.tsx +31 -0
  326. package/src/icons/icon-cvv-visa-mc.tsx +31 -0
  327. package/src/icons/icon-error.css.ts +27 -0
  328. package/src/icons/icon-error.tsx +207 -0
  329. package/src/icons/icon-info.tsx +86 -0
  330. package/src/icons/icon-mastercard.tsx +36 -0
  331. package/src/icons/icon-success-vivo-new.tsx +51 -0
  332. package/src/icons/icon-success-vivo.tsx +36 -0
  333. package/src/icons/icon-success.tsx +211 -0
  334. package/src/icons/icon-visa.tsx +32 -0
  335. package/src/image.css.ts +48 -0
  336. package/src/image.tsx +345 -0
  337. package/src/index.tsx +2466 -0
  338. package/src/inline.css.ts +131 -0
  339. package/src/inline.tsx +135 -0
  340. package/src/integer-field.tsx +93 -0
  341. package/src/list.css.ts +281 -0
  342. package/src/list.tsx +963 -0
  343. package/src/loading-bar.css.ts +69 -0
  344. package/src/loading-bar.tsx +25 -0
  345. package/src/loading-screen.css.ts +114 -0
  346. package/src/loading-screen.tsx +376 -0
  347. package/src/logo-blau-shell.tsx +30 -0
  348. package/src/logo-blau.tsx +60 -0
  349. package/src/logo-common.tsx +29 -0
  350. package/src/logo-esimflag-shell.tsx +30 -0
  351. package/src/logo-esimflag.tsx +56 -0
  352. package/src/logo-movistar-new-shell.tsx +30 -0
  353. package/src/logo-movistar-new.tsx +85 -0
  354. package/src/logo-movistar-shell.tsx +30 -0
  355. package/src/logo-movistar.tsx +63 -0
  356. package/src/logo-o2-new-shell.tsx +26 -0
  357. package/src/logo-o2-new.tsx +27 -0
  358. package/src/logo-o2-shell.tsx +26 -0
  359. package/src/logo-o2.tsx +27 -0
  360. package/src/logo-telefonica-shell.tsx +30 -0
  361. package/src/logo-telefonica.tsx +95 -0
  362. package/src/logo-tu-shell.tsx +26 -0
  363. package/src/logo-tu.tsx +28 -0
  364. package/src/logo-vivo-shell.tsx +30 -0
  365. package/src/logo-vivo.tsx +53 -0
  366. package/src/logo.css.ts +33 -0
  367. package/src/logo.tsx +313 -0
  368. package/src/master-detail-layout.tsx +28 -0
  369. package/src/maybe-dismissable.css.ts +37 -0
  370. package/src/maybe-dismissable.tsx +58 -0
  371. package/src/media-queries.css.ts +67 -0
  372. package/src/menu.css.ts +132 -0
  373. package/src/menu.tsx +468 -0
  374. package/src/meter.tsx +516 -0
  375. package/src/modal-context-provider.tsx +45 -0
  376. package/src/month-field.tsx +124 -0
  377. package/src/mosaic.css.ts +73 -0
  378. package/src/mosaic.tsx +205 -0
  379. package/src/navigation-bar.css.ts +558 -0
  380. package/src/navigation-bar.tsx +1637 -0
  381. package/src/navigation-breadcrumbs.css.ts +22 -0
  382. package/src/navigation-breadcrumbs.tsx +69 -0
  383. package/src/negative-box.tsx +15 -0
  384. package/src/nestable-context.tsx +139 -0
  385. package/src/overlay.tsx +86 -0
  386. package/src/overscroll-color-context.tsx +141 -0
  387. package/src/package-version.tsx +2 -0
  388. package/src/password-field.tsx +126 -0
  389. package/src/phone-number-field-lite.tsx +265 -0
  390. package/src/phone-number-field.tsx +171 -0
  391. package/src/pin-field.css.ts +90 -0
  392. package/src/pin-field.tsx +346 -0
  393. package/src/placeholder.tsx +41 -0
  394. package/src/popover.css.ts +8 -0
  395. package/src/popover.tsx +85 -0
  396. package/src/portal.tsx +43 -0
  397. package/src/progress-bar.css.ts +61 -0
  398. package/src/progress-bar.tsx +174 -0
  399. package/src/radio-button.css.ts +174 -0
  400. package/src/radio-button.tsx +322 -0
  401. package/src/rating.css.ts +128 -0
  402. package/src/rating.tsx +351 -0
  403. package/src/responsive-layout.css.ts +162 -0
  404. package/src/responsive-layout.tsx +106 -0
  405. package/src/screen-reader-only.css.ts +27 -0
  406. package/src/screen-reader-only.tsx +33 -0
  407. package/src/screen-size-context-provider.tsx +96 -0
  408. package/src/screen-size-context.tsx +23 -0
  409. package/src/search-field.tsx +126 -0
  410. package/src/select.css.ts +226 -0
  411. package/src/select.tsx +513 -0
  412. package/src/sheet-action-row.css.ts +33 -0
  413. package/src/sheet-actions-list.tsx +113 -0
  414. package/src/sheet-actions.tsx +95 -0
  415. package/src/sheet-common.css.ts +254 -0
  416. package/src/sheet-common.tsx +402 -0
  417. package/src/sheet-info.css.ts +19 -0
  418. package/src/sheet-info.tsx +127 -0
  419. package/src/sheet-native.tsx +189 -0
  420. package/src/sheet-radio-list.tsx +118 -0
  421. package/src/sheet-root.tsx +127 -0
  422. package/src/sheet-types.tsx +94 -0
  423. package/src/sheet-web.tsx +140 -0
  424. package/src/skeleton-base.tsx +38 -0
  425. package/src/skeletons.css.ts +56 -0
  426. package/src/skeletons.tsx +133 -0
  427. package/src/skins/blau.tsx +724 -0
  428. package/src/skins/constants.tsx +10 -0
  429. package/src/skins/defaults.tsx +104 -0
  430. package/src/skins/esimflag.tsx +728 -0
  431. package/src/skins/movistar-new.tsx +735 -0
  432. package/src/skins/movistar.tsx +740 -0
  433. package/src/skins/o2-new.tsx +731 -0
  434. package/src/skins/o2.tsx +727 -0
  435. package/src/skins/skin-contract.css.ts +380 -0
  436. package/src/skins/telefonica.tsx +768 -0
  437. package/src/skins/tu.tsx +741 -0
  438. package/src/skins/types/colors.tsx +286 -0
  439. package/src/skins/types/index.tsx +153 -0
  440. package/src/skins/utils.tsx +66 -0
  441. package/src/skins/vivo-new.tsx +745 -0
  442. package/src/skins/vivo.tsx +720 -0
  443. package/src/skip-link.css.ts +34 -0
  444. package/src/skip-link.tsx +52 -0
  445. package/src/slider.css.ts +181 -0
  446. package/src/slider.tsx +384 -0
  447. package/src/snackbar-context.tsx +98 -0
  448. package/src/snackbar-native.ts +37 -0
  449. package/src/snackbar.css.ts +176 -0
  450. package/src/snackbar.tsx +258 -0
  451. package/src/spinner.css.ts +66 -0
  452. package/src/spinner.tsx +136 -0
  453. package/src/sprinkles.css.ts +83 -0
  454. package/src/square.css.ts +15 -0
  455. package/src/square.tsx +55 -0
  456. package/src/stack.css.ts +44 -0
  457. package/src/stack.tsx +79 -0
  458. package/src/stacking-group.css.ts +15 -0
  459. package/src/stacking-group.tsx +82 -0
  460. package/src/stepper.css.ts +233 -0
  461. package/src/stepper.tsx +156 -0
  462. package/src/switch-component.css.ts +181 -0
  463. package/src/switch-component.tsx +187 -0
  464. package/src/tab-focus.tsx +68 -0
  465. package/src/table-actions-header.tsx +21 -0
  466. package/src/table-cell-text.tsx +35 -0
  467. package/src/table.css.ts +297 -0
  468. package/src/table.tsx +398 -0
  469. package/src/tabs.css.ts +212 -0
  470. package/src/tabs.tsx +263 -0
  471. package/src/tag.css.ts +42 -0
  472. package/src/tag.tsx +161 -0
  473. package/src/test-utils/environment/setup-ssr.tsx +10 -0
  474. package/src/test-utils/fail-test-on-console-error.tsx +22 -0
  475. package/src/test-utils/index.tsx +341 -0
  476. package/src/test-utils/setup-ssr-test-env.tsx +13 -0
  477. package/src/test-utils/ssr.tsx +197 -0
  478. package/src/text-field-base.css.ts +416 -0
  479. package/src/text-field-base.tsx +628 -0
  480. package/src/text-field-components.css.ts +159 -0
  481. package/src/text-field-components.tsx +225 -0
  482. package/src/text-field.tsx +118 -0
  483. package/src/text-link.css.ts +83 -0
  484. package/src/text-link.tsx +85 -0
  485. package/src/text-tokens.tsx +708 -0
  486. package/src/text.css.ts +60 -0
  487. package/src/text.tsx +516 -0
  488. package/src/theme-context-provider.tsx +370 -0
  489. package/src/theme-context.css.ts +3 -0
  490. package/src/theme-context.tsx +8 -0
  491. package/src/theme-variant-context.tsx +51 -0
  492. package/src/theme.tsx +184 -0
  493. package/src/time-field.tsx +99 -0
  494. package/src/timeline.css.ts +135 -0
  495. package/src/timeline.tsx +250 -0
  496. package/src/timer.css.ts +99 -0
  497. package/src/timer.tsx +420 -0
  498. package/src/title.tsx +119 -0
  499. package/src/tooltip-context-provider.tsx +57 -0
  500. package/src/tooltip.css.ts +106 -0
  501. package/src/tooltip.tsx +649 -0
  502. package/src/touchable.css.ts +56 -0
  503. package/src/touchable.tsx +355 -0
  504. package/src/types/font-face.d.ts +47 -0
  505. package/src/types/libs.d.ts +3 -0
  506. package/src/utils/__tests__/analytics-test.tsx +35 -0
  507. package/src/utils/__tests__/browser-test.tsx +28 -0
  508. package/src/utils/__tests__/dom-test.tsx +23 -0
  509. package/src/utils/__tests__/helpers-test.tsx +166 -0
  510. package/src/utils/analytics.tsx +28 -0
  511. package/src/utils/animation.tsx +201 -0
  512. package/src/utils/aspect-ratio-support.css.ts +28 -0
  513. package/src/utils/aspect-ratio-support.tsx +141 -0
  514. package/src/utils/browser.tsx +9 -0
  515. package/src/utils/color.tsx +46 -0
  516. package/src/utils/common.tsx +27 -0
  517. package/src/utils/credit-card.tsx +46 -0
  518. package/src/utils/css.tsx +25 -0
  519. package/src/utils/document-visibility.tsx +52 -0
  520. package/src/utils/dom.tsx +155 -0
  521. package/src/utils/environment.tsx +6 -0
  522. package/src/utils/headings.tsx +18 -0
  523. package/src/utils/helpers.tsx +182 -0
  524. package/src/utils/keys.tsx +8 -0
  525. package/src/utils/locale.tsx +27 -0
  526. package/src/utils/platform.tsx +94 -0
  527. package/src/utils/region-code.tsx +1 -0
  528. package/src/utils/renders-element.tsx +6 -0
  529. package/src/utils/time.tsx +22 -0
  530. package/src/utils/types.tsx +19 -0
  531. package/src/utils/utility-types.tsx +8 -0
  532. package/src/video.css.ts +11 -0
  533. package/src/video.tsx +355 -0
  534. package/src/vivinho-loading-animation/in-lottie.json +402 -0
  535. package/src/vivinho-loading-animation/index.tsx +90 -0
  536. package/src/vivinho-loading-animation/out-lottie.json +575 -0
  537. package/src/vivinho-loading-animation/pulse-lottie.json +551 -0
  538. package/src/vivinho-loading-animation/vivinho-loading-animation.css.ts +15 -0
  539. package/src/vivinho-loading-animation/wave-lottie.json +2829 -0
@@ -0,0 +1,1300 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import IconChevronLeftRegular from './generated/mistica-icons/icon-chevron-left-regular';
4
+ import IconChevronRightRegular from './generated/mistica-icons/icon-chevron-right-regular';
5
+ import {useIsInViewport, useIsomorphicLayoutEffect, useScreenSize, useTheme} from './hooks';
6
+ import Inline from './inline';
7
+ import classNames from 'classnames';
8
+ import {ThemeVariant, useThemeVariant} from './theme-variant-context';
9
+ import {getPrefixedDataAttributes, listenResize} from './utils/dom';
10
+ import {isAndroid, isIos, isRunningAcceptanceTest} from './utils/platform';
11
+ import {useDocumentVisibility} from './utils/document-visibility';
12
+ import * as styles from './carousel.css';
13
+ import * as mediaStyles from './image.css';
14
+ import {useDesktopContainerType} from './desktop-container-type-context';
15
+ import {VIVO_NEW_SKIN} from './skins/constants';
16
+ import {applyCssVars} from './utils/css';
17
+ import {ResetResponsiveLayout} from './responsive-layout';
18
+ import {InternalIconButton, ToggleIconButton} from './icon-button';
19
+ import IconPauseFilled from './generated/mistica-icons/icon-pause-filled';
20
+ import IconPlayFilled from './generated/mistica-icons/icon-play-filled';
21
+ import IconReloadRegular from './generated/mistica-icons/icon-reload-regular';
22
+ import * as tokens from './text-tokens';
23
+ import {isClientSide} from './utils/environment';
24
+
25
+ import type {DesktopContainerType} from './desktop-container-type-context';
26
+ import type {ByBreakpoint, DataAttributes} from './utils/types';
27
+
28
+ const useShouldAutoplay = (
29
+ autoplay: boolean,
30
+ ref: React.RefObject<HTMLElement | null>
31
+ ): {isAutoplayEnabled: boolean; shouldAutoplay: boolean; setShouldAutoPlay: (enabled: boolean) => void} => {
32
+ const reducedMotion = isClientSide()
33
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
34
+ : false;
35
+ const [isAutoplayEnabled, setIsAutoplayEnabled] = React.useState(!!autoplay && !reducedMotion);
36
+
37
+ const isDocumentVisible = useDocumentVisibility();
38
+ const isInViewport = useIsInViewport(ref, false);
39
+ return {
40
+ isAutoplayEnabled: isAutoplayEnabled && !!autoplay,
41
+ shouldAutoplay: isInViewport && isDocumentVisible && !!autoplay && isAutoplayEnabled,
42
+ setShouldAutoPlay: setIsAutoplayEnabled,
43
+ };
44
+ };
45
+
46
+ const throwMissingProviderError = () => {
47
+ throw new Error('You must wrap your component with a CarouselContextProvider to use CarouselContext');
48
+ };
49
+ const defaultBulletProps = {currentIndex: 0, numPages: 0};
50
+ const defaultAutoplayProps = {
51
+ isAutoplayEnabled: false,
52
+ isAtLastPage: false,
53
+ onAutoplayChanged: throwMissingProviderError,
54
+ };
55
+ const defaultPageControlsProps = {
56
+ setShouldAutoplay: throwMissingProviderError,
57
+ prevArrowEnabled: false,
58
+ nextArrowEnabled: false,
59
+ };
60
+
61
+ type GoToPage = (pageIndex: number, animate?: boolean) => void;
62
+ type SetIsAutoplayEnabled = (isAutoplayEnabled: boolean) => void;
63
+
64
+ type PageBulletsProps = {
65
+ currentIndex: number;
66
+ numPages: number | ByBreakpoint<number>;
67
+ };
68
+
69
+ type AutoplayControlProps = {
70
+ isAutoplayEnabled: boolean;
71
+ isAtLastPage: boolean;
72
+ onAutoplayChanged: (autoplay: boolean) => void;
73
+ };
74
+
75
+ type PageControlsProps = {
76
+ setShouldAutoplay: (autoplay: boolean) => void;
77
+ prevArrowEnabled: boolean;
78
+ nextArrowEnabled: boolean;
79
+ };
80
+
81
+ type CarouselControls = {
82
+ goPrev: () => void;
83
+ goNext: () => void;
84
+ goToPage: GoToPage;
85
+ autoplayControlProps: AutoplayControlProps;
86
+ pageControlsProps: PageControlsProps;
87
+ bulletsProps: PageBulletsProps;
88
+ };
89
+
90
+ const CarouselContext = React.createContext<CarouselControls>({
91
+ goPrev: throwMissingProviderError,
92
+ goNext: throwMissingProviderError,
93
+ goToPage: throwMissingProviderError,
94
+ bulletsProps: defaultBulletProps,
95
+ autoplayControlProps: defaultAutoplayProps,
96
+ pageControlsProps: defaultPageControlsProps,
97
+ });
98
+
99
+ const CarouselControlsSetterContext = React.createContext<{
100
+ setGoPrev: (goPrev: () => void) => void;
101
+ setGoNext: (goNext: () => void) => void;
102
+ setGoToPage: (goToPage: GoToPage) => void;
103
+ setBulletsProps: (bulletsProps: PageBulletsProps) => void;
104
+ setAutoplayControlProps: (autoplayControlProps: AutoplayControlProps) => void;
105
+ setPageControlsProps: (pageControlsProps: PageControlsProps) => void;
106
+ setIsAutoplayEnabledSetter: (isAutoplayEnabledSetter: SetIsAutoplayEnabled) => void;
107
+ } | null>(null);
108
+
109
+ export const CarouselContextProvider = ({children}: {children: React.ReactNode}): JSX.Element => {
110
+ const [bulletsProps, setBulletsProps] = React.useState<PageBulletsProps>(defaultBulletProps);
111
+ const [autoplayControlProps, setAutoplayControlProps] =
112
+ React.useState<AutoplayControlProps>(defaultAutoplayProps);
113
+ const [pageControlsProps, setPageControlsProps] =
114
+ React.useState<PageControlsProps>(defaultPageControlsProps);
115
+ const goPrevRef = React.useRef<() => void>(throwMissingProviderError);
116
+ const goNextRef = React.useRef<() => void>(throwMissingProviderError);
117
+ const goToPageRef = React.useRef<GoToPage>(throwMissingProviderError);
118
+ const setIsAutoplayEnabledRef =
119
+ React.useRef<(isAutoplayEnabled: boolean) => void>(throwMissingProviderError);
120
+
121
+ const controls = React.useMemo<CarouselControls>(
122
+ () => ({
123
+ goPrev: () => {
124
+ goPrevRef.current();
125
+ },
126
+ goNext: () => {
127
+ goNextRef.current();
128
+ },
129
+ goToPage: (pageIndex, animate = true) => {
130
+ goToPageRef.current(pageIndex, animate);
131
+ },
132
+ bulletsProps,
133
+ autoplayControlProps,
134
+ pageControlsProps,
135
+ }),
136
+ [bulletsProps, autoplayControlProps, pageControlsProps]
137
+ );
138
+
139
+ return (
140
+ <CarouselContext.Provider value={controls}>
141
+ <CarouselControlsSetterContext.Provider
142
+ value={{
143
+ setGoPrev: (goPrev) => {
144
+ goPrevRef.current = goPrev;
145
+ },
146
+ setGoNext: (goNext) => {
147
+ goNextRef.current = goNext;
148
+ },
149
+ setGoToPage: (goToPage) => {
150
+ goToPageRef.current = goToPage;
151
+ },
152
+ setBulletsProps,
153
+ setAutoplayControlProps,
154
+ setPageControlsProps,
155
+ setIsAutoplayEnabledSetter: (isAutoplayEnabledSetter) => {
156
+ setIsAutoplayEnabledRef.current = isAutoplayEnabledSetter;
157
+ },
158
+ }}
159
+ >
160
+ {children}
161
+ </CarouselControlsSetterContext.Provider>
162
+ </CarouselContext.Provider>
163
+ );
164
+ };
165
+
166
+ export const useCarouselContext = (): CarouselControls => React.useContext(CarouselContext);
167
+ export const CarouselContextConsumer = CarouselContext.Consumer;
168
+
169
+ export const PageBullets = ({currentIndex, numPages}: PageBulletsProps): JSX.Element => {
170
+ const themeVariant = useThemeVariant();
171
+ const {isTablet, isDesktopOrBigger} = useScreenSize();
172
+ const pagesCount =
173
+ typeof numPages === 'number'
174
+ ? numPages
175
+ : isDesktopOrBigger
176
+ ? numPages.desktop
177
+ : isTablet
178
+ ? numPages.tablet ?? numPages.mobile
179
+ : numPages.mobile;
180
+
181
+ const getClassNames = (bulletIndex: number) => {
182
+ const classNames: {[key: string]: boolean} = {};
183
+
184
+ if (themeVariant === 'brand' || themeVariant === 'media') {
185
+ classNames[currentIndex === bulletIndex ? styles.bulletActiveBrand : styles.bulletBrand] = true;
186
+ } else if (themeVariant === 'negative') {
187
+ classNames[currentIndex === bulletIndex ? styles.bulletActiveNegative : styles.bulletNegative] =
188
+ true;
189
+ } else {
190
+ classNames[currentIndex === bulletIndex ? styles.bulletActive : styles.bullet] = true;
191
+ }
192
+
193
+ if (currentIndex === bulletIndex) {
194
+ classNames[styles.bulletActiveSizing] = true;
195
+ return classNames;
196
+ }
197
+
198
+ const distanceToCurrent = Math.abs(bulletIndex - currentIndex);
199
+
200
+ if (pagesCount <= styles.VISIBLE_BULLETS || distanceToCurrent === 1) {
201
+ classNames[styles.bulletInactiveSizing] = true;
202
+ return classNames;
203
+ }
204
+
205
+ const isFirstOrLastItemActive = currentIndex === 0 || currentIndex === pagesCount - 1;
206
+
207
+ if (isFirstOrLastItemActive) {
208
+ classNames[styles.bulletInactiveSizing] = distanceToCurrent === 2;
209
+ classNames[styles.bulletInactiveMediumSizing] = distanceToCurrent === 3;
210
+ classNames[styles.bulletInactiveSmallSizing] = distanceToCurrent > 3;
211
+
212
+ return classNames;
213
+ }
214
+
215
+ classNames[styles.bulletInactiveMediumSizing] = distanceToCurrent === 2;
216
+ classNames[styles.bulletInactiveSmallSizing] = distanceToCurrent > 2;
217
+
218
+ return classNames;
219
+ };
220
+
221
+ const getLeftOffsetPosition = (bulletIndex: number) => {
222
+ if (currentIndex + 2 < styles.VISIBLE_BULLETS) {
223
+ return bulletIndex;
224
+ }
225
+
226
+ if (pagesCount - currentIndex + 1 < styles.VISIBLE_BULLETS) {
227
+ return bulletIndex - (pagesCount - styles.VISIBLE_BULLETS);
228
+ }
229
+
230
+ return bulletIndex - (currentIndex - 2);
231
+ };
232
+
233
+ return (
234
+ <div
235
+ {...getPrefixedDataAttributes({'component-name': 'PageBullets', testid: 'PageBullets'})}
236
+ className={classNames(styles.bulletsScrollableContainerBase, {
237
+ [styles.bulletsScrollableContainer]: pagesCount > styles.VISIBLE_BULLETS,
238
+ })}
239
+ >
240
+ {Array.from({length: pagesCount}, (_, i: number) => {
241
+ const bulletLeftOffsetPosition = getLeftOffsetPosition(i);
242
+
243
+ return (
244
+ <div
245
+ className={classNames(
246
+ {
247
+ [styles.scrollableBullet]: pagesCount > styles.VISIBLE_BULLETS,
248
+ },
249
+ typeof numPages === 'number'
250
+ ? {[styles.bulletButton]: true, [styles.bulletVisibility]: i < numPages}
251
+ : {
252
+ [styles.bulletButton]: true,
253
+ [styles.bulletVisibilityMobile]: i < numPages.mobile,
254
+ [styles.bulletVisibilityTablet]:
255
+ i < (numPages.tablet ?? numPages.mobile),
256
+ [styles.bulletVisibilityDesktop]: i < numPages.desktop,
257
+ }
258
+ )}
259
+ key={i}
260
+ data-testid={currentIndex === i ? 'active-bullet' : 'bullet'}
261
+ style={{
262
+ ...applyCssVars({
263
+ [styles.vars.desktopBulletLeftPosition]:
264
+ `${bulletLeftOffsetPosition * styles.LARGE_BULLET_FULL_SIZE}px`,
265
+ [styles.vars.mobileBulletLeftPosition]:
266
+ `${bulletLeftOffsetPosition * styles.SMALL_BULLET_FULL_SIZE}px`,
267
+ }),
268
+ }}
269
+ >
270
+ <div className={classNames(getClassNames(i))} />
271
+ </div>
272
+ );
273
+ })}
274
+ </div>
275
+ );
276
+ };
277
+
278
+ const useControlLabel = (type: 'prev' | 'next', pageIndex?: number, pageCount?: number) => {
279
+ const {texts, t} = useTheme();
280
+
281
+ const hasPageInfo = pageIndex !== undefined && pageCount !== undefined;
282
+ const isFirstPage = hasPageInfo && pageIndex === 0;
283
+ const isLastPage = hasPageInfo && pageIndex === pageCount - 1;
284
+
285
+ const getPageNumber = (pageIndex: number) => {
286
+ if (type === 'prev') {
287
+ return isFirstPage ? pageIndex + 1 : pageIndex;
288
+ }
289
+ return isLastPage ? pageIndex + 1 : pageIndex + 2;
290
+ };
291
+
292
+ const pageNumberText = hasPageInfo
293
+ ? `, ${t(texts.carouselPageNumber || tokens.carouselPageNumber, getPageNumber(pageIndex), pageCount)}`
294
+ : '';
295
+
296
+ if (type === 'prev') {
297
+ return isFirstPage
298
+ ? (texts.carouselFirstButton || t(tokens.carouselFirstButton)) + pageNumberText
299
+ : (texts.carouselPrevButton || t(tokens.carouselPrevButton)) + pageNumberText;
300
+ }
301
+
302
+ return isLastPage
303
+ ? (texts.carouselLastButton || t(tokens.carouselLastButton)) + pageNumberText
304
+ : (texts.carouselNextButton || t(tokens.carouselNextButton)) + pageNumberText;
305
+ };
306
+
307
+ type CarouselPageControlsProps = PageControlsProps & {
308
+ bleedLeft?: boolean;
309
+ bleedRight?: boolean;
310
+ goPrev: () => void;
311
+ goNext: () => void;
312
+ pagesCount?: number;
313
+ currentPageIndex?: number;
314
+ };
315
+
316
+ export const CarouselPageControls = ({
317
+ bleedLeft,
318
+ bleedRight,
319
+ goPrev,
320
+ goNext,
321
+ setShouldAutoplay,
322
+ prevArrowEnabled,
323
+ nextArrowEnabled,
324
+ pagesCount,
325
+ currentPageIndex,
326
+ }: CarouselPageControlsProps): JSX.Element => {
327
+ const variant = useThemeVariant();
328
+ const prevPageLabel = useControlLabel('prev', currentPageIndex, pagesCount);
329
+ const nextPageLabel = useControlLabel('next', currentPageIndex, pagesCount);
330
+
331
+ return (
332
+ <Inline space={variant === 'media' ? 16 : 8}>
333
+ <InternalIconButton
334
+ Icon={IconChevronLeftRegular}
335
+ aria-label={prevPageLabel}
336
+ type="neutral"
337
+ backgroundType={variant === 'media' ? 'transparent' : 'soft'}
338
+ small
339
+ bleedLeft={bleedLeft}
340
+ onPress={() => {
341
+ goPrev();
342
+ setShouldAutoplay(false);
343
+ }}
344
+ aria-disabled={!prevArrowEnabled}
345
+ />
346
+ <InternalIconButton
347
+ Icon={IconChevronRightRegular}
348
+ aria-label={nextPageLabel}
349
+ type="neutral"
350
+ backgroundType={variant === 'media' ? 'transparent' : 'soft'}
351
+ small
352
+ bleedRight={bleedRight}
353
+ onPress={() => {
354
+ goNext();
355
+ setShouldAutoplay(false);
356
+ }}
357
+ aria-disabled={!nextArrowEnabled}
358
+ />
359
+ </Inline>
360
+ );
361
+ };
362
+
363
+ type CarouselAutoplayControlProps = AutoplayControlProps & {
364
+ bleedLeft?: boolean;
365
+ bleedRight?: boolean;
366
+ };
367
+
368
+ export const CarouselAutoplayControl = ({
369
+ isAutoplayEnabled,
370
+ isAtLastPage,
371
+ onAutoplayChanged,
372
+ bleedLeft = false,
373
+ bleedRight = false,
374
+ }: CarouselAutoplayControlProps): JSX.Element => {
375
+ const {texts, t} = useTheme();
376
+ return (
377
+ <ToggleIconButton
378
+ checkedProps={{
379
+ Icon: IconPauseFilled,
380
+ type: 'neutral',
381
+ 'aria-label': texts.carouselPauseAutoplay || t(tokens.carouselPauseAutoplay),
382
+ }}
383
+ uncheckedProps={{
384
+ Icon: isAtLastPage ? IconReloadRegular : IconPlayFilled,
385
+ type: 'neutral',
386
+ 'aria-label': isAtLastPage
387
+ ? texts.carouselReloadAutoplay || t(tokens.carouselReloadAutoplay)
388
+ : texts.carouselEnableAutoplay || t(tokens.carouselEnableAutoplay),
389
+ }}
390
+ small
391
+ bleedLeft={bleedLeft}
392
+ bleedRight={bleedRight}
393
+ onChange={onAutoplayChanged}
394
+ checked={isAutoplayEnabled}
395
+ />
396
+ );
397
+ };
398
+
399
+ type DesktopItemsPerPage = {small?: number; medium?: number; large?: number} | number;
400
+ type ItemsPerPageProp = {mobile?: number; tablet?: number; desktop?: DesktopItemsPerPage} | number;
401
+
402
+ const selectDesktopItemsPerPage = (
403
+ containerType: DesktopContainerType,
404
+ defaultItemsPerPage: {small: number; medium: number; large: number},
405
+ itemsPerPage?: DesktopItemsPerPage
406
+ ): number => {
407
+ if (!itemsPerPage) {
408
+ return defaultItemsPerPage[containerType];
409
+ }
410
+ if (typeof itemsPerPage === 'number') {
411
+ return itemsPerPage;
412
+ }
413
+ return itemsPerPage[containerType] || defaultItemsPerPage[containerType];
414
+ };
415
+
416
+ const normalizeItemsPerPage = (
417
+ desktopContainerType: DesktopContainerType,
418
+ itemsPerPage?: ItemsPerPageProp
419
+ ): {
420
+ mobile: number;
421
+ tablet: number;
422
+ desktop: number;
423
+ } => {
424
+ const defaultItemsPerPage = {mobile: 1, tablet: 2, desktop: {small: 1, medium: 2, large: 3}};
425
+ if (!itemsPerPage) {
426
+ return {
427
+ ...defaultItemsPerPage,
428
+ desktop: selectDesktopItemsPerPage(desktopContainerType, defaultItemsPerPage.desktop),
429
+ };
430
+ }
431
+
432
+ if (typeof itemsPerPage === 'number') {
433
+ return {
434
+ mobile: itemsPerPage,
435
+ tablet: itemsPerPage,
436
+ desktop: itemsPerPage,
437
+ };
438
+ }
439
+
440
+ const itemsPerPageDesktop = selectDesktopItemsPerPage(
441
+ desktopContainerType,
442
+ defaultItemsPerPage.desktop,
443
+ itemsPerPage.desktop
444
+ );
445
+
446
+ return {
447
+ ...defaultItemsPerPage,
448
+ ...itemsPerPage,
449
+ desktop: itemsPerPageDesktop,
450
+ };
451
+ };
452
+
453
+ const calcPagesScrollPositions = (itemsScrollPosition: Array<number>, numPages: number) => {
454
+ if (itemsScrollPosition.length === 0) {
455
+ return [];
456
+ }
457
+
458
+ const itemsPerPage = Math.ceil(itemsScrollPosition.length / numPages);
459
+ const pagesScrollPositions = [];
460
+ for (let i = 0; i < itemsScrollPosition.length; i += itemsPerPage) {
461
+ pagesScrollPositions.push(itemsScrollPosition[i]);
462
+ }
463
+ pagesScrollPositions[pagesScrollPositions.length - 1] =
464
+ itemsScrollPosition[itemsScrollPosition.length - itemsPerPage];
465
+
466
+ return pagesScrollPositions;
467
+ };
468
+
469
+ const calcCurrentPageIndex = (scrollPosition: number, pagesScrollPositions: Array<number>) => {
470
+ const middlePageScrollPositions = [];
471
+ for (let i = 0; i < pagesScrollPositions.length; i++) {
472
+ if (i === 0) {
473
+ middlePageScrollPositions.push(pagesScrollPositions[0]);
474
+ } else {
475
+ middlePageScrollPositions.push((pagesScrollPositions[i] + pagesScrollPositions[i - 1]) / 2);
476
+ }
477
+ }
478
+ for (let i = middlePageScrollPositions.length - 1; i >= 0; i--) {
479
+ if (scrollPosition - middlePageScrollPositions[i] >= -1) {
480
+ return i;
481
+ }
482
+ }
483
+ return 0;
484
+ };
485
+
486
+ const DEFAULT_AUTOPLAY_TIME = 5000;
487
+
488
+ type BaseCarouselProps = {
489
+ items: ReadonlyArray<React.ReactNode>;
490
+ itemStyle?: React.CSSProperties;
491
+ itemClassName?: string;
492
+ withBullets?: boolean;
493
+ /**
494
+ * @deprecated use CarouselContextProvider and CarouselContextConsumer to provide bullets props to custom bullets component.
495
+ * See an example here: https://mistica-web.vercel.app/?path=/story/components-carousels-carousel--with-carousel-context
496
+ */
497
+ renderBullets?: (bulletsProps: PageBulletsProps) => React.ReactNode;
498
+ initialActiveItem?: number;
499
+ itemsPerPage?: ItemsPerPageProp;
500
+ /** scrolls one page by default */
501
+ itemsToScroll?: number;
502
+ mobilePageOffset?: 'regular' | 'large';
503
+ /** If true, scroll snap doesn't apply and the user has a free scroll */
504
+ free?: boolean;
505
+ gap?: number;
506
+ /** centered mode only applies to mobile. It includes a horizontal padding of half of the size of an item to show the items centered */
507
+ centered?: boolean;
508
+ autoplay?: boolean | {time: number; loop?: boolean};
509
+ withControls?: boolean;
510
+ onPageChange?: (newPageInfo: {pageIndex: number; shownItemIndexes: Array<number>}) => void;
511
+ dataAttributes?: DataAttributes;
512
+ children?: void;
513
+ 'aria-label'?: string;
514
+ 'aria-labelledby'?: string;
515
+ };
516
+
517
+ const BaseCarousel = ({
518
+ items,
519
+ itemStyle,
520
+ itemClassName,
521
+ withBullets,
522
+ renderBullets,
523
+ initialActiveItem,
524
+ itemsPerPage,
525
+ itemsToScroll,
526
+ mobilePageOffset,
527
+ gap,
528
+ free,
529
+ centered,
530
+ autoplay,
531
+ withControls = true,
532
+ onPageChange,
533
+ dataAttributes,
534
+ 'aria-label': ariaLabelProp,
535
+ 'aria-labelledby': ariaLabelledByProp,
536
+ }: BaseCarouselProps): JSX.Element => {
537
+ const {platformOverrides, skinName, texts, t} = useTheme();
538
+
539
+ const desktopContainerType = useDesktopContainerType();
540
+ const itemsPerPageConfig = normalizeItemsPerPage(desktopContainerType || 'large', itemsPerPage);
541
+
542
+ const {isDesktopOrBigger, isTablet} = useScreenSize();
543
+ const mobileOrTabletItemsPerPage = isTablet ? itemsPerPageConfig.tablet : itemsPerPageConfig.mobile;
544
+ const itemsPerPageFloor = Math.max(
545
+ Math.floor(isDesktopOrBigger ? itemsPerPageConfig.desktop : mobileOrTabletItemsPerPage),
546
+ 1
547
+ );
548
+
549
+ const carouselRef = React.useRef<HTMLDivElement>(null);
550
+
551
+ const pagesCountMobile = Math.ceil(items.length / Math.max(Math.floor(itemsPerPageConfig.mobile), 1));
552
+ const pagesCountTablet = Math.ceil(items.length / Math.max(Math.floor(itemsPerPageConfig.tablet), 1));
553
+ const pagesCountDesktop = Math.ceil(items.length / Math.max(Math.floor(itemsPerPageConfig.desktop), 1));
554
+ const pagesCount = Math.ceil(items.length / itemsPerPageFloor);
555
+
556
+ // ScrollRight is initialized to 1 to avoid the next arrow being disabled when the carousel is first rendered.
557
+ // This is required to make the SSR test pass, and taking advantage of the fact that this is the base case (having more than one page)
558
+ const [{scrollLeft, scrollRight}, setScroll] = React.useState({scrollLeft: 0, scrollRight: 1});
559
+
560
+ const [itemScrollPositions, setItemScrollPositions] = React.useState<Array<number>>([]);
561
+
562
+ const pagesScrollPositions = React.useMemo(
563
+ () => calcPagesScrollPositions(itemScrollPositions, pagesCount),
564
+ [itemScrollPositions, pagesCount]
565
+ );
566
+ const scrollPositions = itemsToScroll
567
+ ? calcPagesScrollPositions(itemScrollPositions, Math.ceil(items.length / itemsToScroll))
568
+ : pagesScrollPositions;
569
+
570
+ const nextArrowEnabled = scrollRight !== 0;
571
+ const prevArrowEnabled = scrollLeft !== 0;
572
+
573
+ const {isAutoplayEnabled, shouldAutoplay, setShouldAutoPlay} = useShouldAutoplay(!!autoplay, carouselRef);
574
+
575
+ React.useEffect(() => {
576
+ if (carouselRef.current) {
577
+ const carouselEl = carouselRef.current;
578
+
579
+ const handleCarouselChange = () => {
580
+ const {scrollWidth, clientWidth} = carouselEl;
581
+ const scrollLeft = Math.round(carouselEl.scrollLeft);
582
+
583
+ const scrollRight = Math.round(scrollWidth - (scrollLeft + clientWidth));
584
+
585
+ setScroll({scrollLeft, scrollRight});
586
+ };
587
+
588
+ const calcItemScrollPositions = () => {
589
+ const maxScroll = carouselEl.scrollWidth - carouselEl.clientWidth;
590
+
591
+ setItemScrollPositions(
592
+ Array.from(carouselEl.querySelectorAll('[data-item]')).map((itemEl, idx) => {
593
+ if (idx === items.length - 1) {
594
+ return maxScroll;
595
+ }
596
+ const offsetLeft = (itemEl as HTMLElement).offsetLeft;
597
+ const scrollMargin = parseInt(getComputedStyle(itemEl).scrollMargin);
598
+ const scrollPosition =
599
+ centered && !isDesktopOrBigger ? offsetLeft - itemEl.clientWidth / 2 : offsetLeft;
600
+ return Math.min(scrollPosition - scrollMargin - carouselEl.offsetLeft, maxScroll);
601
+ })
602
+ );
603
+ };
604
+
605
+ handleCarouselChange();
606
+ calcItemScrollPositions();
607
+
608
+ carouselEl.addEventListener('scroll', handleCarouselChange);
609
+ const unlistenResize = listenResize(carouselEl, () => {
610
+ handleCarouselChange();
611
+ calcItemScrollPositions();
612
+ });
613
+
614
+ return () => {
615
+ carouselEl.removeEventListener('scroll', handleCarouselChange);
616
+ unlistenResize();
617
+ };
618
+ }
619
+ return () => {};
620
+ }, [
621
+ itemsPerPageConfig.desktop,
622
+ itemsPerPageConfig.tablet,
623
+ itemsPerPageConfig.mobile,
624
+ pagesCount,
625
+ gap,
626
+ centered,
627
+ isDesktopOrBigger,
628
+ items.length,
629
+ ]);
630
+
631
+ const goToPage = React.useCallback(
632
+ (pageIndex: number, animate = true) => {
633
+ const carouselEl = carouselRef.current;
634
+ if (carouselEl) {
635
+ const scroll = pagesScrollPositions[pageIndex];
636
+ carouselEl.scrollTo({left: scroll, behavior: animate ? 'smooth' : 'auto'});
637
+ }
638
+ },
639
+ [pagesScrollPositions]
640
+ );
641
+
642
+ const goPrev = React.useCallback(() => {
643
+ const carouselEl = carouselRef.current;
644
+ if (carouselEl) {
645
+ const {scrollLeft} = carouselEl;
646
+ const prevPageScrollPosition = [...scrollPositions]
647
+ .reverse()
648
+ .find((pos) => pos - scrollLeft < -1);
649
+ carouselEl.scrollTo({left: prevPageScrollPosition, behavior: 'smooth'});
650
+ }
651
+ }, [scrollPositions]);
652
+
653
+ const goNext = React.useCallback(() => {
654
+ const carouselEl = carouselRef.current;
655
+ if (carouselEl) {
656
+ const {scrollLeft} = carouselEl;
657
+ const nextPageScrollPosition = scrollPositions.find((pos) => pos - scrollLeft > 1);
658
+ carouselEl.scrollTo({left: nextPageScrollPosition, behavior: 'smooth'});
659
+ }
660
+ }, [scrollPositions]);
661
+
662
+ React.useEffect(() => {
663
+ if (initialActiveItem !== undefined) {
664
+ goToPage(Math.floor(initialActiveItem / itemsPerPageFloor), false);
665
+ }
666
+ }, [initialActiveItem, goToPage, itemsPerPageFloor]);
667
+
668
+ const hasAutoplayLoop = (typeof autoplay === 'object' && autoplay.loop) || false;
669
+
670
+ const interactionDetectorRef = React.useRef<{interacting: boolean; left: number}>({
671
+ interacting: false,
672
+ left: 0,
673
+ });
674
+
675
+ React.useEffect(() => {
676
+ if (shouldAutoplay && autoplay) {
677
+ const time = typeof autoplay === 'boolean' ? DEFAULT_AUTOPLAY_TIME : autoplay.time;
678
+ const interval = setInterval(() => {
679
+ if (!interactionDetectorRef.current.interacting) {
680
+ if (scrollRight !== 0) {
681
+ goNext();
682
+ } else if (hasAutoplayLoop) {
683
+ carouselRef.current?.scrollTo({left: 0, behavior: 'smooth'});
684
+ }
685
+ }
686
+ }, time);
687
+ return () => clearInterval(interval);
688
+ }
689
+ }, [autoplay, goNext, scrollRight, shouldAutoplay, hasAutoplayLoop]);
690
+
691
+ const currentPageIndex = calcCurrentPageIndex(scrollLeft, pagesScrollPositions);
692
+
693
+ const INTERACTION_DETECTOR_THRESHOLD = 20; // pixels
694
+
695
+ React.useEffect(() => {
696
+ if (currentPageIndex === pagesCount - 1 && !hasAutoplayLoop) {
697
+ setShouldAutoPlay(false);
698
+ }
699
+ }, [currentPageIndex, pagesCount, setShouldAutoPlay, hasAutoplayLoop]);
700
+
701
+ const pageInitialized = React.useRef<boolean>(!initialActiveItem);
702
+ const lastPageIndex = React.useRef<number>(0);
703
+
704
+ React.useEffect(() => {
705
+ if (onPageChange) {
706
+ const lastShownItemIndex = Math.min(
707
+ (currentPageIndex + 1) * itemsPerPageFloor - 1,
708
+ items.length - 1
709
+ );
710
+ const shownItemIndexes = [];
711
+ for (let i = 0; i < itemsPerPageFloor; i++) {
712
+ const idx = lastShownItemIndex - i;
713
+ if (idx >= 0) {
714
+ shownItemIndexes.unshift(idx);
715
+ }
716
+ }
717
+
718
+ if (!pageInitialized.current) {
719
+ pageInitialized.current = shownItemIndexes.includes(initialActiveItem || 0);
720
+ } else if (lastPageIndex.current !== currentPageIndex) {
721
+ onPageChange({pageIndex: currentPageIndex, shownItemIndexes});
722
+ }
723
+
724
+ lastPageIndex.current = currentPageIndex;
725
+ }
726
+ }, [currentPageIndex, items.length, itemsPerPageFloor, initialActiveItem, onPageChange]);
727
+
728
+ const controlsSetter = React.useContext(CarouselControlsSetterContext);
729
+
730
+ const bulletsProps = React.useMemo(
731
+ () => ({
732
+ currentIndex: currentPageIndex,
733
+ numPages: {
734
+ mobile: pagesCountMobile,
735
+ tablet: pagesCountTablet,
736
+ desktop: pagesCountDesktop,
737
+ },
738
+ }),
739
+ [currentPageIndex, pagesCountDesktop, pagesCountMobile, pagesCountTablet]
740
+ );
741
+ const autoplayControlProps = React.useMemo(
742
+ () => ({
743
+ isAutoplayEnabled,
744
+ isAtLastPage: currentPageIndex === pagesCount - 1,
745
+ onAutoplayChanged: setShouldAutoPlay,
746
+ }),
747
+ [isAutoplayEnabled, currentPageIndex, pagesCount, setShouldAutoPlay]
748
+ );
749
+ const pageControlsProps = React.useMemo(
750
+ () => ({
751
+ setShouldAutoplay: setShouldAutoPlay,
752
+ prevArrowEnabled,
753
+ nextArrowEnabled,
754
+ }),
755
+ [setShouldAutoPlay, prevArrowEnabled, nextArrowEnabled]
756
+ );
757
+
758
+ React.useEffect(() => {
759
+ if (controlsSetter) {
760
+ controlsSetter.setGoPrev(goPrev);
761
+ controlsSetter.setGoNext(goNext);
762
+ controlsSetter.setGoToPage(goToPage);
763
+ controlsSetter.setBulletsProps(bulletsProps);
764
+ controlsSetter.setAutoplayControlProps(autoplayControlProps);
765
+ controlsSetter.setPageControlsProps(pageControlsProps);
766
+ controlsSetter.setIsAutoplayEnabledSetter(setShouldAutoPlay);
767
+ }
768
+ }, [
769
+ controlsSetter,
770
+ goNext,
771
+ goPrev,
772
+ bulletsProps,
773
+ autoplayControlProps,
774
+ pageControlsProps,
775
+ goToPage,
776
+ prevArrowEnabled,
777
+ nextArrowEnabled,
778
+ autoplay,
779
+ isAutoplayEnabled,
780
+ setShouldAutoPlay,
781
+ ]);
782
+
783
+ let bullets: React.ReactNode = null;
784
+
785
+ if (renderBullets) {
786
+ bullets = renderBullets({numPages: pagesCount, currentIndex: currentPageIndex});
787
+ } else if (withBullets) {
788
+ bullets = <PageBullets {...bulletsProps} />;
789
+ }
790
+
791
+ const largePageOffset = '64px';
792
+ const vivoNewMobilePageOffset = '36px';
793
+
794
+ const bulletsContainer = (
795
+ <div
796
+ className={classNames(
797
+ styles.carouselBullets,
798
+ // when renderBullets is provided, we let the consumer decide if the bullets should be hidden
799
+ !renderBullets && {
800
+ [styles.noCarouselBulletsDesktop]: pagesCountDesktop <= 1,
801
+ [styles.noCarouselBulletsTablet]: pagesCountTablet <= 1,
802
+ [styles.noCarouselBulletsMobile]: pagesCountMobile <= 1,
803
+ }
804
+ )}
805
+ >
806
+ {bullets}
807
+ </div>
808
+ );
809
+
810
+ return (
811
+ <div
812
+ {...getPrefixedDataAttributes({
813
+ 'component-name': 'Carousel',
814
+ testid: 'Carousel',
815
+ ...dataAttributes,
816
+ })}
817
+ className={styles.carouselComponentContainer}
818
+ role="region"
819
+ aria-label={
820
+ ariaLabelProp
821
+ ? `${ariaLabelProp}, ${texts.carouselRegion || t(tokens.carouselRegion)}`
822
+ : undefined
823
+ }
824
+ aria-labelledby={ariaLabelProp ? undefined : ariaLabelledByProp}
825
+ >
826
+ <div
827
+ className={classNames(styles.carouselControlsVisibility, {
828
+ [styles.carouselControlsVisibilityMobile]: pagesCountMobile > 1,
829
+ [styles.carouselControlsVisibilityTablet]: pagesCountTablet > 1,
830
+ [styles.carouselControlsVisibilityDesktop]: pagesCountDesktop > 1,
831
+ })}
832
+ >
833
+ {withControls ? (
834
+ <Inline space="between" alignItems="center" className={styles.carouselControlsContainer}>
835
+ {!!autoplay && (
836
+ <div className={styles.carouselAutoplayControlContainer}>
837
+ <CarouselAutoplayControl
838
+ isAutoplayEnabled={isAutoplayEnabled}
839
+ isAtLastPage={currentPageIndex === pagesCount - 1}
840
+ onAutoplayChanged={(autoplayEnabled: boolean) => {
841
+ if (!nextArrowEnabled && autoplayEnabled) {
842
+ goToPage(0);
843
+ }
844
+ setShouldAutoPlay(autoplayEnabled);
845
+ }}
846
+ />
847
+ </div>
848
+ )}
849
+ {bulletsContainer}
850
+ <div className={styles.carouselPagesControlsContainer}>
851
+ <CarouselPageControls
852
+ goNext={goNext}
853
+ goPrev={goPrev}
854
+ setShouldAutoplay={setShouldAutoPlay}
855
+ prevArrowEnabled={prevArrowEnabled}
856
+ nextArrowEnabled={nextArrowEnabled}
857
+ pagesCount={pagesCount}
858
+ currentPageIndex={currentPageIndex}
859
+ />
860
+ </div>
861
+ </Inline>
862
+ ) : (
863
+ bullets && (
864
+ <Inline space="around" className={styles.carouselControlsContainer}>
865
+ {bulletsContainer}
866
+ </Inline>
867
+ )
868
+ )}
869
+ </div>
870
+ <div className={styles.carouselContainer}>
871
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
872
+ <div
873
+ className={classNames(styles.carousel, {
874
+ [styles.centeredCarousel]: centered,
875
+ [styles.carouselWithScrollMobile]: pagesCountMobile > 1,
876
+ [styles.carouselWithScrollTablet]: pagesCountTablet > 1,
877
+ })}
878
+ role="list"
879
+ style={{
880
+ ...applyCssVars({
881
+ [styles.vars.itemsPerPageDesktop]: String(itemsPerPageConfig.desktop),
882
+ [styles.vars.itemsPerPageTablet]: String(itemsPerPageConfig.tablet),
883
+ [styles.vars.itemsPerPageMobile]: String(itemsPerPageConfig.mobile),
884
+ ...(mobilePageOffset === 'large'
885
+ ? {[styles.vars.mobilePageOffset]: largePageOffset}
886
+ : skinName === VIVO_NEW_SKIN
887
+ ? {[styles.vars.mobilePageOffset]: vivoNewMobilePageOffset}
888
+ : {}),
889
+ ...(gap !== undefined ? {[styles.vars.gap]: String(gap)} : {}),
890
+ }),
891
+ scrollSnapType: free ? 'initial' : 'x mandatory',
892
+
893
+ // Hack to fix https://jira.tid.es/browse/NOVUMCC-8988
894
+ // there is a webkit rendering bug that causes a half pixel white line to appear at
895
+ // the bottom of the scrollable area in retina displays when it has a height with
896
+ // decimals. This extra padding avoids that line to partially cover the carousel
897
+ // slides border:
898
+ paddingBottom:
899
+ isIos(platformOverrides) && !isRunningAcceptanceTest(platformOverrides)
900
+ ? 0.5
901
+ : undefined,
902
+ }}
903
+ ref={carouselRef}
904
+ onTouchStart={(e) => {
905
+ interactionDetectorRef.current.left = e.currentTarget.scrollLeft;
906
+ interactionDetectorRef.current.interacting = true;
907
+ }}
908
+ onTouchEnd={(e) => {
909
+ interactionDetectorRef.current.interacting = false;
910
+ if (
911
+ Math.abs(e.currentTarget.scrollLeft - interactionDetectorRef.current.left) >
912
+ INTERACTION_DETECTOR_THRESHOLD
913
+ ) {
914
+ setShouldAutoPlay(false);
915
+ }
916
+ }}
917
+ onKeyDown={() => {
918
+ setShouldAutoPlay(false);
919
+ }}
920
+ >
921
+ {items.map((item, index) => (
922
+ <div
923
+ key={index}
924
+ className={classNames(styles.carouselItem, itemClassName)}
925
+ style={{
926
+ ...itemStyle,
927
+ scrollSnapStop: isAndroid(platformOverrides) ? 'always' : 'normal',
928
+ }}
929
+ role="listitem"
930
+ data-item
931
+ >
932
+ {item}
933
+ </div>
934
+ ))}
935
+ </div>
936
+ </div>
937
+ </div>
938
+ );
939
+ };
940
+
941
+ type CarouselProps = {
942
+ items: ReadonlyArray<React.ReactNode>;
943
+ itemStyle?: React.CSSProperties;
944
+ itemClassName?: string;
945
+ withBullets?: boolean;
946
+ /**
947
+ * @deprecated use CarouselContextProvider and CarouselContextConsumer to provide bullets props to custom bullets component.
948
+ * See an example here: https://mistica-web.vercel.app/?path=/story/components-carousels-carousel--with-carousel-context
949
+ */
950
+ renderBullets?: (bulletsProps: PageBulletsProps) => React.ReactNode;
951
+ initialActiveItem?: number;
952
+ itemsPerPage?: ItemsPerPageProp;
953
+ /** scrolls one page by default */
954
+ itemsToScroll?: number;
955
+ mobilePageOffset?: 'regular' | 'large';
956
+ /** If true, scroll snap doesn't apply and the user has a free scroll */
957
+ free?: boolean;
958
+ autoplay?: boolean | {time: number; loop?: boolean};
959
+ withControls?: boolean;
960
+ onPageChange?: (newPageInfo: {pageIndex: number; shownItemIndexes: Array<number>}) => void;
961
+ dataAttributes?: DataAttributes;
962
+ 'aria-label'?: string;
963
+ 'aria-labelledby'?: string;
964
+
965
+ children?: void;
966
+ };
967
+
968
+ /**
969
+ * This is a workaround for a bug that happens when rendering a carousel in a webview inside a hidden tab (eg. Explore).
970
+ * The webview has a width of 0 when it's hidden, and the carousel doesn't render correctly when it's first shown.
971
+ * This hook forces the carousel to re-render when the webview is shown, by adding a key to the carousel component.
972
+ *
973
+ * This workaround gets executed only once, when the webview with changes from 0 to another value and then it removes the listener.
974
+ * Related issue: https://jira.tid.es/browse/WEB-1644
975
+ */
976
+ const useWorkaroundForZeroWidthWebView = () => {
977
+ const [key, setKey] = React.useState(1);
978
+ React.useEffect(() => {
979
+ const handler = () => {
980
+ if (window.innerWidth !== 0) {
981
+ setKey((k) => k + 1);
982
+ window.removeEventListener('resize', handler);
983
+ }
984
+ };
985
+ // Set the listener only when the webview has zero width
986
+ if (window.innerWidth === 0) {
987
+ window.addEventListener('resize', handler);
988
+ }
989
+ return () => {
990
+ window.removeEventListener('resize', handler);
991
+ };
992
+ }, []);
993
+ return key;
994
+ };
995
+
996
+ export const Carousel = (props: CarouselProps): JSX.Element => {
997
+ const key = useWorkaroundForZeroWidthWebView();
998
+ return <BaseCarousel {...props} key={key} />;
999
+ };
1000
+
1001
+ type CenteredCarouselProps = {
1002
+ items: ReadonlyArray<React.ReactNode>;
1003
+ itemStyle?: React.CSSProperties;
1004
+ itemClassName?: string;
1005
+ withBullets?: boolean;
1006
+ withControls?: boolean;
1007
+ /**
1008
+ * @deprecated use CarouselContextProvider and CarouselContextConsumer to provide bullets props to custom bullets component.
1009
+ * See an example here: https://mistica-web.vercel.app/?path=/story/components-carousels-carousel--with-carousel-context
1010
+ */
1011
+ renderBullets?: (bulletsProps: PageBulletsProps) => React.ReactNode;
1012
+ initialActiveItem?: number;
1013
+ onPageChange?: (newPageInfo: {pageIndex: number; shownItemIndexes: Array<number>}) => void;
1014
+ dataAttributes?: DataAttributes;
1015
+ 'aria-label'?: string;
1016
+ 'aria-labelledby'?: string;
1017
+
1018
+ children?: void;
1019
+ };
1020
+
1021
+ export const CenteredCarousel = ({
1022
+ items,
1023
+ itemStyle,
1024
+ itemClassName,
1025
+ withBullets,
1026
+ renderBullets,
1027
+ withControls = true,
1028
+ initialActiveItem,
1029
+ onPageChange,
1030
+ dataAttributes,
1031
+ 'aria-label': ariaLabelProp,
1032
+ 'aria-labelledby': ariaLabelledByProp,
1033
+ }: CenteredCarouselProps): JSX.Element => {
1034
+ const key = useWorkaroundForZeroWidthWebView();
1035
+ return (
1036
+ <BaseCarousel
1037
+ key={key}
1038
+ items={items}
1039
+ itemStyle={itemStyle}
1040
+ itemClassName={itemClassName}
1041
+ itemsPerPage={{mobile: 1, tablet: 1, desktop: 3}}
1042
+ centered
1043
+ itemsToScroll={1}
1044
+ gap={0}
1045
+ withBullets={withBullets}
1046
+ renderBullets={renderBullets}
1047
+ withControls={withControls}
1048
+ initialActiveItem={initialActiveItem}
1049
+ onPageChange={onPageChange}
1050
+ dataAttributes={dataAttributes}
1051
+ aria-label={ariaLabelProp}
1052
+ aria-labelledby={ariaLabelledByProp}
1053
+ />
1054
+ );
1055
+ };
1056
+
1057
+ type SlideshowProps = {
1058
+ items: ReadonlyArray<React.ReactNode>;
1059
+ withBullets?: boolean;
1060
+ autoplay?: boolean | {time: number; loop?: boolean};
1061
+ initialPageIndex?: number;
1062
+ withControls?: boolean;
1063
+ onPageChange?: (newPageIndex: number) => void;
1064
+ dataAttributes?: DataAttributes;
1065
+ inverseBullets?: boolean;
1066
+
1067
+ children?: void;
1068
+ };
1069
+
1070
+ /**
1071
+ * This context is used internally to let other components (Hero) now if they are rendered inside a Slideshow
1072
+ * to make some tweaks in the UI
1073
+ */
1074
+ const SlideshowContext = React.createContext<{withBullets: boolean} | undefined>(undefined);
1075
+
1076
+ export const useSlideshowContext = (): {withBullets: boolean} | undefined =>
1077
+ React.useContext(SlideshowContext);
1078
+
1079
+ export const Slideshow = ({
1080
+ items,
1081
+ withBullets,
1082
+ withControls = true,
1083
+ autoplay,
1084
+ initialPageIndex = 0,
1085
+ onPageChange,
1086
+ dataAttributes,
1087
+ inverseBullets = true,
1088
+ }: SlideshowProps): JSX.Element => {
1089
+ const {platformOverrides} = useTheme();
1090
+ const controlsSetter = React.useContext(CarouselControlsSetterContext);
1091
+
1092
+ const carouselRef = React.useRef<HTMLDivElement>(null);
1093
+
1094
+ // ScrollRight is initialized to 1 to avoid the next arrow being disabled when the carousel is first rendered.
1095
+ // This is required to make the SSR test pass, and taking advantage of the fact that this is the base case (having more than one page)
1096
+ const [{scrollLeft, scrollRight}, setScroll] = React.useState({scrollLeft: 0, scrollRight: 1});
1097
+ const nextArrowEnabled = scrollRight !== 0;
1098
+ const prevArrowEnabled = scrollLeft !== 0;
1099
+
1100
+ const goPrev = React.useCallback(() => {
1101
+ const carouselEl = carouselRef.current;
1102
+ if (carouselEl) {
1103
+ carouselEl.scrollBy({left: -carouselEl.clientWidth, behavior: 'smooth'});
1104
+ }
1105
+ }, []);
1106
+
1107
+ const goNext = React.useCallback(() => {
1108
+ const carouselEl = carouselRef.current;
1109
+ if (carouselEl) {
1110
+ carouselEl.scrollBy({left: carouselEl.clientWidth, behavior: 'smooth'});
1111
+ }
1112
+ }, []);
1113
+
1114
+ const goToPage = React.useCallback(
1115
+ (pageIndex: number, animate = true) => {
1116
+ const carouselEl = carouselRef.current;
1117
+ if (carouselEl) {
1118
+ carouselEl.scrollTo({
1119
+ left: carouselEl.clientWidth * pageIndex,
1120
+ behavior: animate ? 'smooth' : 'auto',
1121
+ });
1122
+ }
1123
+ },
1124
+ [carouselRef]
1125
+ );
1126
+
1127
+ const currentIndex = carouselRef.current
1128
+ ? Math.floor((scrollLeft + carouselRef.current.clientWidth / 2) / carouselRef.current.clientWidth)
1129
+ : 0;
1130
+
1131
+ useIsomorphicLayoutEffect(() => {
1132
+ const carouselEl = carouselRef.current;
1133
+ if (carouselEl) {
1134
+ const handleCarouselChange = () => {
1135
+ const {scrollWidth, clientWidth} = carouselEl;
1136
+ const scrollLeft = Math.round(carouselEl.scrollLeft);
1137
+ const scrollRight = Math.round(scrollWidth - (scrollLeft + clientWidth));
1138
+ setScroll({scrollLeft, scrollRight});
1139
+ };
1140
+
1141
+ handleCarouselChange();
1142
+
1143
+ carouselEl.addEventListener('scroll', handleCarouselChange);
1144
+ const unlistenResize = listenResize(carouselEl, handleCarouselChange);
1145
+
1146
+ return () => {
1147
+ carouselEl.removeEventListener('scroll', handleCarouselChange);
1148
+ unlistenResize();
1149
+ };
1150
+ }
1151
+ }, [items.length]);
1152
+
1153
+ const {isAutoplayEnabled, shouldAutoplay, setShouldAutoPlay} = useShouldAutoplay(!!autoplay, carouselRef);
1154
+
1155
+ const hasAutoplayLoop = (typeof autoplay === 'object' && autoplay.loop) || false;
1156
+
1157
+ React.useEffect(() => {
1158
+ if (shouldAutoplay && autoplay) {
1159
+ const time = typeof autoplay === 'boolean' ? DEFAULT_AUTOPLAY_TIME : autoplay.time;
1160
+ const interval = setInterval(() => {
1161
+ if (scrollRight !== 0) {
1162
+ goNext();
1163
+ } else if (hasAutoplayLoop) {
1164
+ carouselRef.current?.scrollTo({left: 0, behavior: 'smooth'});
1165
+ }
1166
+ }, time);
1167
+ return () => clearInterval(interval);
1168
+ }
1169
+ }, [autoplay, goNext, scrollRight, shouldAutoplay, hasAutoplayLoop]);
1170
+
1171
+ React.useEffect(() => {
1172
+ if (currentIndex === items.length - 1 && !hasAutoplayLoop) {
1173
+ setShouldAutoPlay(false);
1174
+ }
1175
+ }, [currentIndex, items.length, setShouldAutoPlay, hasAutoplayLoop]);
1176
+
1177
+ const pageInitialized = React.useRef(false);
1178
+ const lastPageIndex = React.useRef(0);
1179
+
1180
+ React.useEffect(() => {
1181
+ if (onPageChange) {
1182
+ if (!pageInitialized.current) {
1183
+ pageInitialized.current = initialPageIndex === currentIndex;
1184
+ } else if (lastPageIndex.current !== currentIndex) {
1185
+ onPageChange(currentIndex);
1186
+ }
1187
+ }
1188
+
1189
+ lastPageIndex.current = currentIndex;
1190
+ }, [currentIndex, initialPageIndex, onPageChange]);
1191
+
1192
+ React.useEffect(() => {
1193
+ const carouselEl = carouselRef.current;
1194
+ if (initialPageIndex !== undefined && carouselEl && !pageInitialized.current) {
1195
+ carouselEl.scrollTo({left: carouselEl.clientWidth * initialPageIndex});
1196
+ }
1197
+ }, [initialPageIndex]);
1198
+
1199
+ const bulletsProps = React.useMemo<PageBulletsProps>(
1200
+ () => ({
1201
+ currentIndex,
1202
+ numPages: items.length,
1203
+ }),
1204
+ [currentIndex, items.length]
1205
+ );
1206
+
1207
+ React.useEffect(() => {
1208
+ if (controlsSetter) {
1209
+ controlsSetter.setGoPrev(goPrev);
1210
+ controlsSetter.setGoNext(goNext);
1211
+ controlsSetter.setGoToPage(goToPage);
1212
+ controlsSetter.setBulletsProps(bulletsProps);
1213
+ }
1214
+ }, [controlsSetter, goNext, goPrev, bulletsProps, goToPage]);
1215
+
1216
+ const bulletsContainer = withBullets && (
1217
+ <div className={styles.slideshowBulletsContainer}>
1218
+ <ThemeVariant variant={inverseBullets ? 'inverse' : 'default'}>
1219
+ <PageBullets {...bulletsProps} />
1220
+ </ThemeVariant>
1221
+ </div>
1222
+ );
1223
+
1224
+ const onlyControls = !withBullets && !autoplay;
1225
+
1226
+ return (
1227
+ <SlideshowContext.Provider value={{withBullets: !!withBullets}}>
1228
+ <ResetResponsiveLayout skipDesktop>
1229
+ <div
1230
+ className={classNames(styles.slideshowContainer, {
1231
+ [styles.slideshowWithBullets]: !!withBullets,
1232
+ })}
1233
+ {...getPrefixedDataAttributes(dataAttributes, 'SlideShow')}
1234
+ >
1235
+ {items.length > 1 &&
1236
+ (withControls ? (
1237
+ <ThemeVariant variant="media">
1238
+ <Inline
1239
+ space={onlyControls ? 0 : 'between'}
1240
+ alignItems="center"
1241
+ className={classNames(styles.slideshowControlsContainer, {
1242
+ [styles.slideshowControlsContainerSingleItem]: onlyControls,
1243
+ })}
1244
+ >
1245
+ {!!autoplay && (
1246
+ <div className={styles.slideshowAutoplayControlContainer}>
1247
+ <CarouselAutoplayControl
1248
+ isAutoplayEnabled={isAutoplayEnabled}
1249
+ isAtLastPage={currentIndex === items.length - 1}
1250
+ onAutoplayChanged={(autoplayEnabled: boolean) => {
1251
+ if (
1252
+ currentIndex === items.length - 1 &&
1253
+ autoplayEnabled
1254
+ ) {
1255
+ goToPage(0);
1256
+ }
1257
+ setShouldAutoPlay(autoplayEnabled);
1258
+ }}
1259
+ />
1260
+ </div>
1261
+ )}
1262
+ {bulletsContainer}
1263
+ <CarouselPageControls
1264
+ goNext={goNext}
1265
+ goPrev={goPrev}
1266
+ setShouldAutoplay={setShouldAutoPlay}
1267
+ prevArrowEnabled={prevArrowEnabled}
1268
+ nextArrowEnabled={nextArrowEnabled}
1269
+ pagesCount={items.length}
1270
+ currentPageIndex={currentIndex}
1271
+ />
1272
+ </Inline>
1273
+ </ThemeVariant>
1274
+ ) : (
1275
+ withBullets && (
1276
+ <Inline space="around" className={styles.slideshowControlsContainer}>
1277
+ {bulletsContainer}
1278
+ </Inline>
1279
+ )
1280
+ ))}
1281
+ <div style={applyCssVars({[mediaStyles.vars.mediaBorderRadius]: '0px'})}>
1282
+ <div className={styles.slideshow} ref={carouselRef}>
1283
+ {items.map((item, index) => (
1284
+ <div
1285
+ key={index}
1286
+ className={styles.slideshowItem}
1287
+ style={{
1288
+ scrollSnapStop: isAndroid(platformOverrides) ? 'always' : 'normal',
1289
+ }}
1290
+ >
1291
+ {item}
1292
+ </div>
1293
+ ))}
1294
+ </div>
1295
+ </div>
1296
+ </div>
1297
+ </ResetResponsiveLayout>
1298
+ </SlideshowContext.Provider>
1299
+ );
1300
+ };