@windstream/react-shared-components 0.1.93 → 0.1.94

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 (298) hide show
  1. package/README.md +635 -635
  2. package/dist/contentful/index.esm.js +3 -3
  3. package/dist/contentful/index.esm.js.map +1 -1
  4. package/dist/contentful/index.js +3 -3
  5. package/dist/contentful/index.js.map +1 -1
  6. package/dist/core.d.ts +6 -6
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.esm.js +13 -5
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.js +13 -5
  11. package/dist/index.js.map +1 -1
  12. package/dist/next/index.esm.js +2 -2
  13. package/dist/next/index.esm.js.map +1 -1
  14. package/dist/next/index.js +2 -2
  15. package/dist/next/index.js.map +1 -1
  16. package/dist/styles.css +1 -1
  17. package/dist/utils/index.esm.js +1 -1
  18. package/dist/utils/index.esm.js.map +1 -1
  19. package/dist/utils/index.js +1 -1
  20. package/dist/utils/index.js.map +1 -1
  21. package/package.json +191 -185
  22. package/src/components/accordion/Accordion.stories.tsx +230 -230
  23. package/src/components/accordion/index.test.tsx +270 -0
  24. package/src/components/accordion/index.tsx +70 -70
  25. package/src/components/accordion/types.ts +12 -12
  26. package/src/components/alert-card/AlertCard.stories.tsx +171 -171
  27. package/src/components/alert-card/index.test.tsx +152 -0
  28. package/src/components/alert-card/index.tsx +41 -41
  29. package/src/components/alert-card/types.ts +13 -13
  30. package/src/components/animation-wrapper/index.test.tsx +424 -0
  31. package/src/components/animation-wrapper/index.tsx +129 -129
  32. package/src/components/animation-wrapper/types.ts +11 -11
  33. package/src/components/brand-button/BrandButton.stories.tsx +223 -223
  34. package/src/components/brand-button/helpers.ts +35 -35
  35. package/src/components/brand-button/index.test.tsx +292 -0
  36. package/src/components/brand-button/index.tsx +120 -120
  37. package/src/components/brand-button/types.ts +38 -38
  38. package/src/components/button/Button.stories.tsx +108 -108
  39. package/src/components/button/index.test.tsx +91 -0
  40. package/src/components/button/index.tsx +27 -27
  41. package/src/components/button/types.ts +14 -14
  42. package/src/components/call-button/CallButton.stories.tsx +324 -324
  43. package/src/components/call-button/index.test.tsx +260 -0
  44. package/src/components/call-button/index.tsx +106 -106
  45. package/src/components/call-button/types.ts +16 -16
  46. package/src/components/checkbox/Checkbox.stories.tsx +247 -247
  47. package/src/components/checkbox/index.test.tsx +252 -0
  48. package/src/components/checkbox/index.tsx +197 -197
  49. package/src/components/checkbox/types.ts +27 -27
  50. package/src/components/checklist/Checklist.stories.tsx +150 -150
  51. package/src/components/checklist/index.test.tsx +231 -0
  52. package/src/components/checklist/index.tsx +96 -61
  53. package/src/components/checklist/types.ts +23 -17
  54. package/src/components/collapse/Collapse.stories.tsx +255 -255
  55. package/src/components/collapse/index.test.tsx +277 -0
  56. package/src/components/collapse/index.tsx +47 -46
  57. package/src/components/collapse/types.ts +6 -6
  58. package/src/components/divider/Divider.stories.tsx +205 -205
  59. package/src/components/divider/index.test.tsx +53 -0
  60. package/src/components/divider/index.tsx +22 -22
  61. package/src/components/divider/type.ts +3 -3
  62. package/src/components/image/Image.stories.tsx +113 -113
  63. package/src/components/image/index.test.tsx +174 -0
  64. package/src/components/image/index.tsx +25 -25
  65. package/src/components/image/types.ts +40 -40
  66. package/src/components/input/Input.stories.tsx +325 -325
  67. package/src/components/input/index.test.tsx +348 -0
  68. package/src/components/input/index.tsx +177 -177
  69. package/src/components/input/types.ts +37 -37
  70. package/src/components/link/Link.stories.tsx +163 -163
  71. package/src/components/link/index.test.tsx +199 -0
  72. package/src/components/link/index.tsx +116 -116
  73. package/src/components/link/types.ts +25 -25
  74. package/src/components/list/List.stories.tsx +272 -272
  75. package/src/components/list/index.test.tsx +166 -0
  76. package/src/components/list/index.tsx +88 -88
  77. package/src/components/list/list-item/index.tsx +38 -38
  78. package/src/components/list/list-item/types.ts +13 -13
  79. package/src/components/list/types.ts +29 -29
  80. package/src/components/material-icon/MaterialIcon.stories.tsx +322 -322
  81. package/src/components/material-icon/constants.ts +99 -99
  82. package/src/components/material-icon/index.test.tsx +130 -0
  83. package/src/components/material-icon/index.tsx +47 -47
  84. package/src/components/material-icon/types.ts +31 -31
  85. package/src/components/modal/Modal.stories.tsx +171 -171
  86. package/src/components/modal/index.test.tsx +310 -0
  87. package/src/components/modal/index.tsx +164 -164
  88. package/src/components/modal/types.ts +24 -24
  89. package/src/components/next-image/index.test.tsx +406 -0
  90. package/src/components/next-image/index.tsx +74 -74
  91. package/src/components/next-image/types.ts +1 -1
  92. package/src/components/pagination/index.test.tsx +521 -0
  93. package/src/components/pagination/index.tsx +91 -91
  94. package/src/components/pagination/types.ts +6 -6
  95. package/src/components/radio-button/RadioButton.stories.tsx +307 -307
  96. package/src/components/radio-button/index.test.tsx +151 -0
  97. package/src/components/radio-button/index.tsx +75 -75
  98. package/src/components/radio-button/types.ts +21 -21
  99. package/src/components/see-more/SeeMore.stories.tsx +181 -181
  100. package/src/components/see-more/index.test.tsx +96 -0
  101. package/src/components/see-more/index.tsx +44 -44
  102. package/src/components/see-more/types.ts +4 -4
  103. package/src/components/select/Select.stories.tsx +411 -411
  104. package/src/components/select/index.test.tsx +256 -0
  105. package/src/components/select/index.tsx +155 -155
  106. package/src/components/select/types.ts +36 -36
  107. package/src/components/select-plan-button/SelectPlanButton.stories.tsx +184 -184
  108. package/src/components/select-plan-button/index.test.tsx +173 -0
  109. package/src/components/select-plan-button/index.tsx +63 -63
  110. package/src/components/select-plan-button/types.ts +17 -17
  111. package/src/components/skeleton/Skeleton.stories.tsx +179 -179
  112. package/src/components/skeleton/index.test.tsx +74 -0
  113. package/src/components/skeleton/index.tsx +61 -61
  114. package/src/components/skeleton/types.ts +4 -4
  115. package/src/components/spinner/Spinner.stories.tsx +335 -335
  116. package/src/components/spinner/index.test.tsx +76 -0
  117. package/src/components/spinner/index.tsx +44 -44
  118. package/src/components/spinner/types.ts +5 -5
  119. package/src/components/text/Text.stories.tsx +321 -321
  120. package/src/components/text/index.test.tsx +65 -0
  121. package/src/components/text/index.tsx +25 -25
  122. package/src/components/text/types.ts +45 -45
  123. package/src/components/tooltip/Tooltip.stories.tsx +219 -219
  124. package/src/components/tooltip/index.test.tsx +50 -0
  125. package/src/components/tooltip/index.tsx +74 -74
  126. package/src/components/tooltip/types.ts +7 -7
  127. package/src/components/view-cart-button/ViewCartButton.stories.tsx +252 -252
  128. package/src/components/view-cart-button/index.test.tsx +57 -0
  129. package/src/components/view-cart-button/index.tsx +42 -42
  130. package/src/components/view-cart-button/types.ts +5 -5
  131. package/src/contentful/blocks/accordion/Accordion.stories.mocks.tsx +128 -128
  132. package/src/contentful/blocks/accordion/Accordion.stories.tsx +98 -98
  133. package/src/contentful/blocks/accordion/index.test.tsx +218 -0
  134. package/src/contentful/blocks/accordion/index.tsx +114 -112
  135. package/src/contentful/blocks/accordion/types.ts +34 -34
  136. package/src/contentful/blocks/address-input-banner/index.test.tsx +132 -0
  137. package/src/contentful/blocks/address-input-banner/index.tsx +52 -52
  138. package/src/contentful/blocks/address-input-banner/types.ts +14 -14
  139. package/src/contentful/blocks/anchored-bottom-banner/index.test.tsx +287 -0
  140. package/src/contentful/blocks/anchored-bottom-banner/index.tsx +181 -181
  141. package/src/contentful/blocks/anchored-bottom-banner/types.ts +13 -13
  142. package/src/contentful/blocks/blogs-grid/BlogGrid.stories.mocks.tsx +144 -144
  143. package/src/contentful/blocks/blogs-grid/BlogGrid.stories.tsx +157 -156
  144. package/src/contentful/blocks/blogs-grid/index.test.tsx +355 -0
  145. package/src/contentful/blocks/blogs-grid/index.tsx +134 -134
  146. package/src/contentful/blocks/blogs-grid/types.ts +26 -26
  147. package/src/contentful/blocks/blogs-grid-base/index.test.tsx +274 -0
  148. package/src/contentful/blocks/blogs-grid-base/index.tsx +119 -119
  149. package/src/contentful/blocks/blogs-grid-base/types.ts +36 -36
  150. package/src/contentful/blocks/breadcrumbs/BreadcrumbNavigation.stories.tsx +147 -147
  151. package/src/contentful/blocks/breadcrumbs/index.test.tsx +281 -0
  152. package/src/contentful/blocks/breadcrumbs/index.tsx +95 -95
  153. package/src/contentful/blocks/breadcrumbs/types.ts +8 -8
  154. package/src/contentful/blocks/button/Button.stories.tsx +40 -40
  155. package/src/contentful/blocks/button/index.test.tsx +339 -0
  156. package/src/contentful/blocks/button/index.tsx +131 -131
  157. package/src/contentful/blocks/button/types.ts +39 -39
  158. package/src/contentful/blocks/callout/Callout.stories.tsx +23 -23
  159. package/src/contentful/blocks/callout/index.test.tsx +539 -0
  160. package/src/contentful/blocks/callout/index.tsx +277 -277
  161. package/src/contentful/blocks/callout/types.ts +78 -78
  162. package/src/contentful/blocks/cards/Cards.stories.tsx +23 -23
  163. package/src/contentful/blocks/cards/blog-card/index.test.tsx +218 -0
  164. package/src/contentful/blocks/cards/blog-card/index.tsx +129 -129
  165. package/src/contentful/blocks/cards/blog-card/types.ts +34 -34
  166. package/src/contentful/blocks/cards/floating-image-card/index.test.tsx +201 -0
  167. package/src/contentful/blocks/cards/floating-image-card/index.tsx +119 -119
  168. package/src/contentful/blocks/cards/floating-image-card/types.ts +30 -30
  169. package/src/contentful/blocks/cards/full-image-card/index.test.tsx +216 -0
  170. package/src/contentful/blocks/cards/full-image-card/index.tsx +130 -130
  171. package/src/contentful/blocks/cards/full-image-card/types.ts +29 -29
  172. package/src/contentful/blocks/cards/index.test.tsx +39 -0
  173. package/src/contentful/blocks/cards/index.tsx +13 -13
  174. package/src/contentful/blocks/cards/product-card/index.test.tsx +263 -0
  175. package/src/contentful/blocks/cards/product-card/index.tsx +251 -251
  176. package/src/contentful/blocks/cards/product-card/types.ts +28 -28
  177. package/src/contentful/blocks/cards/simple-card/index.test.tsx +364 -0
  178. package/src/contentful/blocks/cards/simple-card/index.tsx +325 -325
  179. package/src/contentful/blocks/cards/simple-card/types.ts +71 -71
  180. package/src/contentful/blocks/cards/testimonial-card/index.test.tsx +180 -0
  181. package/src/contentful/blocks/cards/testimonial-card/index.tsx +90 -90
  182. package/src/contentful/blocks/cards/testimonial-card/types.tsx +12 -12
  183. package/src/contentful/blocks/cards/types.ts +1 -1
  184. package/src/contentful/blocks/carousel/Carousel.stories.tsx +23 -23
  185. package/src/contentful/blocks/carousel/helper.test.tsx +539 -0
  186. package/src/contentful/blocks/carousel/helper.tsx +494 -494
  187. package/src/contentful/blocks/carousel/index.test.tsx +308 -0
  188. package/src/contentful/blocks/carousel/index.tsx +87 -87
  189. package/src/contentful/blocks/carousel/types.test.ts +16 -0
  190. package/src/contentful/blocks/carousel/types.ts +145 -145
  191. package/src/contentful/blocks/cart-retention-banner/index.test.tsx +409 -0
  192. package/src/contentful/blocks/cart-retention-banner/index.tsx +109 -109
  193. package/src/contentful/blocks/cart-retention-banner/types.ts +11 -11
  194. package/src/contentful/blocks/comparison-table/index.test.tsx +114 -0
  195. package/src/contentful/blocks/comparison-table/index.tsx +29 -29
  196. package/src/contentful/blocks/comparison-table/types.ts +6 -6
  197. package/src/contentful/blocks/cookiebanner/index.test.tsx +277 -0
  198. package/src/contentful/blocks/cookiebanner/index.tsx +146 -146
  199. package/src/contentful/blocks/cookiebanner/type.ts +7 -7
  200. package/src/contentful/blocks/cta-callout/CtaCallout.stories.tsx +46 -46
  201. package/src/contentful/blocks/cta-callout/index.test.tsx +244 -0
  202. package/src/contentful/blocks/cta-callout/index.tsx +73 -73
  203. package/src/contentful/blocks/cta-callout/types.ts +26 -26
  204. package/src/contentful/blocks/dynamic-tabs/index.test.tsx +240 -0
  205. package/src/contentful/blocks/dynamic-tabs/index.tsx +204 -204
  206. package/src/contentful/blocks/dynamic-tabs/types.ts +21 -21
  207. package/src/contentful/blocks/email-input-block/index.test.tsx +213 -0
  208. package/src/contentful/blocks/email-input-block/index.tsx +121 -116
  209. package/src/contentful/blocks/email-input-block/types.ts +16 -16
  210. package/src/contentful/blocks/find-kinetic/FindKinetic.stories.tsx +23 -23
  211. package/src/contentful/blocks/find-kinetic/index.test.tsx +269 -0
  212. package/src/contentful/blocks/find-kinetic/index.tsx +138 -138
  213. package/src/contentful/blocks/find-kinetic/types.ts +20 -20
  214. package/src/contentful/blocks/floating-banner/FloatingBanner.stories.tsx +34 -34
  215. package/src/contentful/blocks/floating-banner/index.test.tsx +246 -0
  216. package/src/contentful/blocks/floating-banner/index.tsx +97 -97
  217. package/src/contentful/blocks/floating-banner/types.ts +22 -22
  218. package/src/contentful/blocks/footer/Footer.stories.tsx +317 -317
  219. package/src/contentful/blocks/footer/index.test.tsx +302 -0
  220. package/src/contentful/blocks/footer/index.tsx +91 -91
  221. package/src/contentful/blocks/footer/types.ts +13 -13
  222. package/src/contentful/blocks/image-promo-bar/ImagePromoBar.stories.tsx +23 -23
  223. package/src/contentful/blocks/image-promo-bar/helper.test.tsx +61 -0
  224. package/src/contentful/blocks/image-promo-bar/helper.tsx +28 -28
  225. package/src/contentful/blocks/image-promo-bar/index.test.tsx +467 -0
  226. package/src/contentful/blocks/image-promo-bar/index.tsx +246 -246
  227. package/src/contentful/blocks/image-promo-bar/types.ts +44 -44
  228. package/src/contentful/blocks/image-promo-bar/vimeo-embed.test.tsx +142 -0
  229. package/src/contentful/blocks/image-promo-bar/vimeo-embed.tsx +93 -93
  230. package/src/contentful/blocks/image-promo-bar/youtube-embed.test.tsx +104 -0
  231. package/src/contentful/blocks/image-promo-bar/youtube-embed.tsx +46 -46
  232. package/src/contentful/blocks/modal/constants.ts +53 -53
  233. package/src/contentful/blocks/modal/index.test.tsx +209 -0
  234. package/src/contentful/blocks/modal/index.tsx +108 -108
  235. package/src/contentful/blocks/modal/types.ts +12 -12
  236. package/src/contentful/blocks/navigation/Navigation.stories.mocks.tsx +78 -78
  237. package/src/contentful/blocks/navigation/Navigation.stories.tsx +138 -138
  238. package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.test.tsx +208 -0
  239. package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.tsx +141 -141
  240. package/src/contentful/blocks/navigation/index.test.tsx +924 -0
  241. package/src/contentful/blocks/navigation/index.tsx +569 -569
  242. package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.test.tsx +131 -0
  243. package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.tsx +82 -82
  244. package/src/contentful/blocks/navigation/types.ts +71 -71
  245. package/src/contentful/blocks/primary-hero/PrimaryHero.stories.tsx +23 -23
  246. package/src/contentful/blocks/primary-hero/index.test.tsx +286 -0
  247. package/src/contentful/blocks/primary-hero/index.tsx +239 -236
  248. package/src/contentful/blocks/primary-hero/types.ts +37 -37
  249. package/src/contentful/blocks/search-block/index.test.tsx +268 -0
  250. package/src/contentful/blocks/search-block/index.tsx +90 -90
  251. package/src/contentful/blocks/search-block/types.ts +15 -15
  252. package/src/contentful/blocks/shape-background-wrapper/ShapeBackgroundWrapper.stories.tsx +26 -26
  253. package/src/contentful/blocks/shape-background-wrapper/index.test.tsx +284 -0
  254. package/src/contentful/blocks/shape-background-wrapper/index.tsx +124 -124
  255. package/src/contentful/blocks/shape-background-wrapper/types.ts +36 -36
  256. package/src/contentful/blocks/text/Text.stories.tsx +23 -23
  257. package/src/contentful/blocks/text/index.test.tsx +36 -0
  258. package/src/contentful/blocks/text/index.tsx +12 -12
  259. package/src/contentful/blocks/text/types.ts +1 -1
  260. package/src/contentful/index.test.ts +45 -0
  261. package/src/contentful/index.ts +105 -105
  262. package/src/global-mocks/contentful/to-document.ts +25 -0
  263. package/src/global-mocks/cookie.ts +48 -0
  264. package/src/global-mocks/cx.ts +37 -0
  265. package/src/global-mocks/index.ts +89 -0
  266. package/src/global-mocks/speed-card-bg.ts +27 -0
  267. package/src/global-mocks/utm.ts +49 -0
  268. package/src/hooks/contentful/use-contentful-rich-text.test.tsx +1758 -0
  269. package/src/hooks/contentful/use-contentful-rich-text.tsx +309 -309
  270. package/src/hooks/contentful/use-processed-check-list.test.tsx +277 -0
  271. package/src/hooks/contentful/use-processed-check-list.ts +63 -63
  272. package/src/hooks/use-body-scroll-lock.test.ts +134 -0
  273. package/src/hooks/use-body-scroll-lock.ts +34 -34
  274. package/src/hooks/use-carousel-swipe.test.ts +393 -0
  275. package/src/hooks/use-carousel-swipe.ts +264 -264
  276. package/src/hooks/use-outside-click.test.ts +142 -0
  277. package/src/hooks/use-outside-click.ts +17 -17
  278. package/src/index.ts +107 -107
  279. package/src/next/index.test.ts +7 -0
  280. package/src/next/index.ts +5 -5
  281. package/src/setupTests.ts +52 -46
  282. package/src/stories/DocsTemplate.tsx +24 -24
  283. package/src/styles/globals.css +343 -343
  284. package/src/types/global.d.ts +9 -9
  285. package/src/types/micro-components.ts +99 -99
  286. package/src/types/utm.ts +49 -49
  287. package/src/utils/contentful/to-document.test.ts +85 -0
  288. package/src/utils/contentful/to-document.ts +24 -24
  289. package/src/utils/cookie.test.ts +180 -0
  290. package/src/utils/cookie.ts +84 -84
  291. package/src/utils/cx.test.ts +90 -0
  292. package/src/utils/cx.ts +49 -49
  293. package/src/utils/index.test.ts +115 -0
  294. package/src/utils/index.ts +41 -41
  295. package/src/utils/speed-card-bg.test.ts +46 -0
  296. package/src/utils/speed-card-bg.ts +24 -24
  297. package/src/utils/utm.test.ts +359 -0
  298. package/src/utils/utm.ts +221 -221
@@ -0,0 +1,277 @@
1
+ import "@testing-library/jest-dom";
2
+
3
+ import React from "react";
4
+ import CookieBanner from "./index";
5
+
6
+ import { act, fireEvent, render, screen } from "@testing-library/react";
7
+
8
+ // Mock cookie utilities
9
+ const mockGetCookie = jest.fn();
10
+ const mockSetCookie = jest.fn();
11
+
12
+ jest.mock("../../../utils/cookie", () => ({
13
+ getCookie: (...args: any[]) => mockGetCookie(...args),
14
+ setCookie: (...args: any[]) => mockSetCookie(...args),
15
+ }));
16
+
17
+ jest.mock("@shared/components/material-icon", () => ({
18
+ MaterialIcon: ({ name }: any) => (
19
+ <span data-testid={`icon-${name}`}>{name}</span>
20
+ ),
21
+ }));
22
+
23
+ jest.mock("@shared/contentful/blocks/button", () => ({
24
+ Button: (props: any) => (
25
+ <button onClick={props.onClick} data-testid="cookie-button">
26
+ {props.children}
27
+ </button>
28
+ ),
29
+ }));
30
+
31
+ describe("CookieBanner", () => {
32
+ const defaultContent = {
33
+ entryName: "Cookie Notice",
34
+ anchorId: "cookie-banner",
35
+ title: "Cookies",
36
+ richText: <p>We use cookies to improve your experience.</p>,
37
+ };
38
+
39
+ beforeEach(() => {
40
+ jest.useFakeTimers();
41
+ mockGetCookie.mockReset();
42
+ mockSetCookie.mockReset();
43
+ // Default: cookie not set (banner should show)
44
+ mockGetCookie.mockReturnValue(null);
45
+ // Reset body classes
46
+ document.body.classList.remove("cookie-banner-visible");
47
+ // Reset CSS custom property
48
+ document.documentElement.style.removeProperty("--cookie-banner-height");
49
+ });
50
+
51
+ afterEach(() => {
52
+ jest.useRealTimers();
53
+ });
54
+
55
+ describe("cookie management", () => {
56
+ it("shows the banner when cookieBannerClosed cookie is not set", () => {
57
+ mockGetCookie.mockReturnValue(null);
58
+ render(<CookieBanner content={defaultContent} />);
59
+ expect(
60
+ screen.getByLabelText("Cookie usage notification")
61
+ ).toBeInTheDocument();
62
+ });
63
+
64
+ it("hides the banner when cookieBannerClosed cookie is true", () => {
65
+ mockGetCookie.mockReturnValue("true");
66
+ const { container } = render(<CookieBanner content={defaultContent} />);
67
+ expect(container.firstChild).toBeNull();
68
+ });
69
+
70
+ it("calls getCookie with cookieBannerClosed key", () => {
71
+ render(<CookieBanner content={defaultContent} />);
72
+ expect(mockGetCookie).toHaveBeenCalledWith("cookieBannerClosed");
73
+ });
74
+
75
+ it("sets cookieBannerClosed cookie on close", () => {
76
+ mockGetCookie.mockReturnValue(null);
77
+ render(<CookieBanner content={defaultContent} />);
78
+ fireEvent.click(screen.getByTestId("cookie-button"));
79
+ expect(mockSetCookie).toHaveBeenCalledWith(
80
+ "cookieBannerClosed",
81
+ true,
82
+ expect.objectContaining({ expires: expect.any(Date) })
83
+ );
84
+ });
85
+
86
+ it("sets cookie with correct expiration (30 days)", () => {
87
+ mockGetCookie.mockReturnValue(null);
88
+ render(<CookieBanner content={defaultContent} />);
89
+
90
+ const now = Date.now();
91
+ fireEvent.click(screen.getByTestId("cookie-button"));
92
+
93
+ const callArgs = mockSetCookie.mock.calls[0];
94
+ const expirationDate = callArgs[2].expires;
95
+ // 43200 minutes * 60 * 1000 = 30 days in milliseconds
96
+ const expectedMs = 43200 * 60 * 1000;
97
+ const diff = expirationDate.getTime() - now;
98
+ expect(diff).toBeGreaterThanOrEqual(expectedMs - 1000);
99
+ expect(diff).toBeLessThanOrEqual(expectedMs + 1000);
100
+ });
101
+ });
102
+
103
+ describe("rendering", () => {
104
+ it("renders with fixed positioning", () => {
105
+ render(<CookieBanner content={defaultContent} />);
106
+ const banner = screen.getByLabelText("Cookie usage notification");
107
+ expect(banner.className).toContain("fixed");
108
+ });
109
+
110
+ it("renders the rich text content", () => {
111
+ render(<CookieBanner content={defaultContent} />);
112
+ expect(
113
+ screen.getByText("We use cookies to improve your experience.")
114
+ ).toBeInTheDocument();
115
+ });
116
+
117
+ it("does not render rich text when richText is falsy", () => {
118
+ const content = { ...defaultContent, richText: null as any };
119
+ render(<CookieBanner content={content} />);
120
+ const banner = screen.getByLabelText("Cookie usage notification");
121
+ // Banner is shown but no rich text inside
122
+ expect(banner.querySelector(".mx-auto")).toBeNull();
123
+ });
124
+
125
+ it("renders close button with close icon", () => {
126
+ render(<CookieBanner content={defaultContent} />);
127
+ expect(screen.getByTestId("icon-close")).toBeInTheDocument();
128
+ });
129
+
130
+ it("has correct z-index class", () => {
131
+ render(<CookieBanner content={defaultContent} />);
132
+ const banner = screen.getByLabelText("Cookie usage notification");
133
+ expect(banner.className).toContain("z-[1000]");
134
+ });
135
+
136
+ it("renders with correct max width", () => {
137
+ render(<CookieBanner content={defaultContent} />);
138
+ const banner = screen.getByLabelText("Cookie usage notification");
139
+ expect(banner.className).toContain("max-w-[350px]");
140
+ });
141
+
142
+ it("renders with correct positioning styles", () => {
143
+ render(<CookieBanner content={defaultContent} />);
144
+ const banner = screen.getByLabelText("Cookie usage notification");
145
+ expect(banner.style.bottom).toBe("0px");
146
+ expect(banner.style.right).toBe("20px");
147
+ });
148
+
149
+ it("has id cookie-banner", () => {
150
+ render(<CookieBanner content={defaultContent} />);
151
+ expect(document.getElementById("cookie-banner")).toBeInTheDocument();
152
+ });
153
+ });
154
+
155
+ describe("close behavior", () => {
156
+ it("hides banner on close button click", () => {
157
+ mockGetCookie.mockReturnValue(null);
158
+ render(<CookieBanner content={defaultContent} />);
159
+ expect(
160
+ screen.getByLabelText("Cookie usage notification")
161
+ ).toBeInTheDocument();
162
+ fireEvent.click(screen.getByTestId("cookie-button"));
163
+ expect(
164
+ screen.queryByLabelText("Cookie usage notification")
165
+ ).not.toBeInTheDocument();
166
+ });
167
+ });
168
+
169
+ describe("body class management", () => {
170
+ it("adds cookie-banner-visible class to body when visible", () => {
171
+ mockGetCookie.mockReturnValue(null);
172
+ render(<CookieBanner content={defaultContent} />);
173
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
174
+ true
175
+ );
176
+ });
177
+
178
+ it("removes cookie-banner-visible class when banner is hidden", () => {
179
+ mockGetCookie.mockReturnValue(null);
180
+ render(<CookieBanner content={defaultContent} />);
181
+ fireEvent.click(screen.getByTestId("cookie-button"));
182
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
183
+ false
184
+ );
185
+ });
186
+
187
+ it("removes cookie-banner-visible class on unmount", () => {
188
+ mockGetCookie.mockReturnValue(null);
189
+ const { unmount } = render(<CookieBanner content={defaultContent} />);
190
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
191
+ true
192
+ );
193
+ unmount();
194
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
195
+ false
196
+ );
197
+ });
198
+ });
199
+
200
+ describe("sticky footer detection", () => {
201
+ it("uses larger margin when anchored-banner element exists", () => {
202
+ const anchoredBanner = document.createElement("div");
203
+ anchoredBanner.id = "anchored-banner";
204
+ document.body.appendChild(anchoredBanner);
205
+
206
+ mockGetCookie.mockReturnValue(null);
207
+ render(<CookieBanner content={defaultContent} />);
208
+ const banner = screen.getByLabelText("Cookie usage notification");
209
+ // marginBottom = 14 * 4 = 56px when anchored-banner exists
210
+ expect(banner.style.marginBottom).toBe("56px");
211
+
212
+ document.body.removeChild(anchoredBanner);
213
+ });
214
+
215
+ it("uses smaller margin when anchored-banner does not exist", () => {
216
+ mockGetCookie.mockReturnValue(null);
217
+ render(<CookieBanner content={defaultContent} />);
218
+ const banner = screen.getByLabelText("Cookie usage notification");
219
+ // marginBottom = 3 * 4 = 12px when no anchored-banner
220
+ expect(banner.style.marginBottom).toBe("12px");
221
+ });
222
+ });
223
+
224
+ describe("CSS custom property for height", () => {
225
+ it("calculates and sets --cookie-banner-height property", () => {
226
+ mockGetCookie.mockReturnValue(null);
227
+ render(<CookieBanner content={defaultContent} />);
228
+
229
+ act(() => {
230
+ jest.advanceTimersByTime(350);
231
+ });
232
+
233
+ // The property should be set (value depends on getBoundingClientRect mock)
234
+ // In jsdom, getBoundingClientRect returns zeros, so spaceFromBottom = innerHeight - 0
235
+ const value = document.documentElement.style.getPropertyValue(
236
+ "--cookie-banner-height"
237
+ );
238
+ // Should be set (even though value may be 0px in jsdom)
239
+ expect(value).toBeDefined();
240
+ });
241
+
242
+ it("removes --cookie-banner-height on unmount", () => {
243
+ mockGetCookie.mockReturnValue(null);
244
+ const { unmount } = render(<CookieBanner content={defaultContent} />);
245
+
246
+ act(() => {
247
+ jest.advanceTimersByTime(350);
248
+ });
249
+
250
+ unmount();
251
+ const value = document.documentElement.style.getPropertyValue(
252
+ "--cookie-banner-height"
253
+ );
254
+ expect(value).toBe("");
255
+ });
256
+
257
+ it("recalculates height on window resize", () => {
258
+ mockGetCookie.mockReturnValue(null);
259
+ render(<CookieBanner content={defaultContent} />);
260
+
261
+ act(() => {
262
+ jest.advanceTimersByTime(350);
263
+ });
264
+
265
+ act(() => {
266
+ window.dispatchEvent(new Event("resize"));
267
+ jest.advanceTimersByTime(200);
268
+ });
269
+
270
+ // Should not throw and property remains set
271
+ const value = document.documentElement.style.getPropertyValue(
272
+ "--cookie-banner-height"
273
+ );
274
+ expect(value).toBeDefined();
275
+ });
276
+ });
277
+ });
@@ -1,146 +1,146 @@
1
- "use client";
2
-
3
- import { useEffect, useRef, useState } from "react";
4
- import { getCookie, setCookie } from "../../../utils/cookie";
5
- import { TypeComponentRichTextFields } from "./type";
6
-
7
- import { MaterialIcon } from "@shared/components/material-icon";
8
- import { Button } from "@shared/contentful/blocks/button";
9
-
10
- export default function CookieBanner({
11
- content,
12
- }: {
13
- content: TypeComponentRichTextFields;
14
- }) {
15
- let marginBottom = 3;
16
- const [isBannerVisible, setIsBannerVisible] = useState(false);
17
- const [isStickyFooterPresent, setStickyFooterPresent] = useState(false);
18
- const bannerRef = useRef<HTMLDivElement>(null);
19
-
20
- useEffect(() => {
21
- const cookieBannerClosed = getCookie("cookieBannerClosed");
22
- if (cookieBannerClosed === "true") {
23
- setIsBannerVisible(false);
24
- } else {
25
- setIsBannerVisible(true);
26
- }
27
- }, []);
28
-
29
- useEffect(() => {
30
- const checkElementPresence = () => {
31
- const element = document.getElementById("anchored-banner");
32
- setStickyFooterPresent(!!element);
33
- };
34
- checkElementPresence();
35
- }, []);
36
-
37
- // Add body class and calculate height for chat positioning
38
- useEffect(() => {
39
- if (isBannerVisible) {
40
- window?.document?.body?.classList.add("cookie-banner-visible");
41
- } else {
42
- window?.document?.body?.classList.remove("cookie-banner-visible");
43
- }
44
-
45
- return () => {
46
- window?.document?.body?.classList.remove("cookie-banner-visible");
47
- };
48
- }, [isBannerVisible]);
49
-
50
- // Calculate cookie banner height and set CSS custom property
51
- useEffect(() => {
52
- if (!isBannerVisible) return;
53
-
54
- const calculateCookieBannerHeight = () => {
55
- if (bannerRef.current) {
56
- const rect = bannerRef.current.getBoundingClientRect();
57
- const viewportHeight = window.innerHeight;
58
-
59
- // Calculate how much space the banner occupies from the bottom of the viewport
60
- // This is: viewport height - distance from top
61
- const spaceFromBottom = viewportHeight - rect.top;
62
-
63
- document.documentElement.style.setProperty(
64
- "--cookie-banner-height",
65
- `${spaceFromBottom}px`
66
- );
67
- }
68
- };
69
-
70
- // Calculate initially after render and after a short delay to ensure layout is complete
71
- const timer1 = setTimeout(calculateCookieBannerHeight, 100);
72
- const timer2 = setTimeout(calculateCookieBannerHeight, 300);
73
-
74
- // Recalculate on window resize with debounce
75
- let resizeTimeout: ReturnType<typeof setTimeout>;
76
- const handleResize = () => {
77
- clearTimeout(resizeTimeout);
78
- resizeTimeout = setTimeout(calculateCookieBannerHeight, 150);
79
- };
80
-
81
- window.addEventListener("resize", handleResize);
82
-
83
- return () => {
84
- clearTimeout(timer1);
85
- clearTimeout(timer2);
86
- clearTimeout(resizeTimeout);
87
- window.removeEventListener("resize", handleResize);
88
- document.documentElement.style.removeProperty("--cookie-banner-height");
89
- };
90
- }, [isBannerVisible, isStickyFooterPresent]);
91
-
92
- const handleClose = () => {
93
- const expirationDate = new Date(Date.now() + 43200 * 60 * 1000);
94
- setIsBannerVisible(false);
95
- setCookie("cookieBannerClosed", true, {
96
- expires: expirationDate,
97
- });
98
- };
99
- if (isStickyFooterPresent) marginBottom = 14;
100
-
101
- // Calculate margin values in pixels for inline styles
102
- const marginBottomPx = marginBottom * 4; // Tailwind: 3 = 12px, 14 = 56px (each unit is 4px)
103
- const marginLeftPx = 12; // mx-3 = 12px
104
-
105
- if (!isBannerVisible) return null;
106
-
107
- return (
108
- <div
109
- id="cookie-banner"
110
- ref={bannerRef}
111
- className="fixed z-[1000] max-w-[350px] rounded-xl bg-white py-5 pl-6 pr-12 text-black shadow-lg ring-1 ring-gray-900/10 md:max-w-[656px]"
112
- style={{
113
- // maxWidth: maxWidth,
114
- bottom: "0px",
115
- right: "20px",
116
- marginBottom: `${marginBottomPx}px`,
117
- marginLeft: `${marginLeftPx}px`,
118
- }}
119
- aria-label="Cookie usage notification"
120
- >
121
- {" "}
122
- {
123
- <Button
124
- showButtonAs="unstyled"
125
- buttonClassName="absolute right-2 top-3 mr-2 mt-0 h-6 w-6 bg-white"
126
- onClick={handleClose}
127
- >
128
- <MaterialIcon name="close" />
129
- </Button>
130
- }
131
- {/* {content ? (
132
- <div className="my-4 mx-auto [&>a]:footnote">
133
- {renderContentfulRichText(
134
- toDocument(content.richText),
135
- false,
136
- "footnote",
137
- "footnote",
138
- )}
139
- </div>
140
- ) : null} */}
141
- {content.richText ? (
142
- <div className="mx-auto my-4 [&>a]:footnote">{content.richText}</div>
143
- ) : null}
144
- </div>
145
- );
146
- }
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { getCookie, setCookie } from "../../../utils/cookie";
5
+ import { TypeComponentRichTextFields } from "./type";
6
+
7
+ import { MaterialIcon } from "@shared/components/material-icon";
8
+ import { Button } from "@shared/contentful/blocks/button";
9
+
10
+ export default function CookieBanner({
11
+ content,
12
+ }: {
13
+ content: TypeComponentRichTextFields;
14
+ }) {
15
+ let marginBottom = 3;
16
+ const [isBannerVisible, setIsBannerVisible] = useState(false);
17
+ const [isStickyFooterPresent, setStickyFooterPresent] = useState(false);
18
+ const bannerRef = useRef<HTMLDivElement>(null);
19
+
20
+ useEffect(() => {
21
+ const cookieBannerClosed = getCookie("cookieBannerClosed");
22
+ if (cookieBannerClosed === "true") {
23
+ setIsBannerVisible(false);
24
+ } else {
25
+ setIsBannerVisible(true);
26
+ }
27
+ }, []);
28
+
29
+ useEffect(() => {
30
+ const checkElementPresence = () => {
31
+ const element = document.getElementById("anchored-banner");
32
+ setStickyFooterPresent(!!element);
33
+ };
34
+ checkElementPresence();
35
+ }, []);
36
+
37
+ // Add body class and calculate height for chat positioning
38
+ useEffect(() => {
39
+ if (isBannerVisible) {
40
+ window?.document?.body?.classList.add("cookie-banner-visible");
41
+ } else {
42
+ window?.document?.body?.classList.remove("cookie-banner-visible");
43
+ }
44
+
45
+ return () => {
46
+ window?.document?.body?.classList.remove("cookie-banner-visible");
47
+ };
48
+ }, [isBannerVisible]);
49
+
50
+ // Calculate cookie banner height and set CSS custom property
51
+ useEffect(() => {
52
+ if (!isBannerVisible) return;
53
+
54
+ const calculateCookieBannerHeight = () => {
55
+ if (bannerRef.current) {
56
+ const rect = bannerRef.current.getBoundingClientRect();
57
+ const viewportHeight = window.innerHeight;
58
+
59
+ // Calculate how much space the banner occupies from the bottom of the viewport
60
+ // This is: viewport height - distance from top
61
+ const spaceFromBottom = viewportHeight - rect.top;
62
+
63
+ document.documentElement.style.setProperty(
64
+ "--cookie-banner-height",
65
+ `${spaceFromBottom}px`
66
+ );
67
+ }
68
+ };
69
+
70
+ // Calculate initially after render and after a short delay to ensure layout is complete
71
+ const timer1 = setTimeout(calculateCookieBannerHeight, 100);
72
+ const timer2 = setTimeout(calculateCookieBannerHeight, 300);
73
+
74
+ // Recalculate on window resize with debounce
75
+ let resizeTimeout: ReturnType<typeof setTimeout>;
76
+ const handleResize = () => {
77
+ clearTimeout(resizeTimeout);
78
+ resizeTimeout = setTimeout(calculateCookieBannerHeight, 150);
79
+ };
80
+
81
+ window.addEventListener("resize", handleResize);
82
+
83
+ return () => {
84
+ clearTimeout(timer1);
85
+ clearTimeout(timer2);
86
+ clearTimeout(resizeTimeout);
87
+ window.removeEventListener("resize", handleResize);
88
+ document.documentElement.style.removeProperty("--cookie-banner-height");
89
+ };
90
+ }, [isBannerVisible, isStickyFooterPresent]);
91
+
92
+ const handleClose = () => {
93
+ const expirationDate = new Date(Date.now() + 43200 * 60 * 1000);
94
+ setIsBannerVisible(false);
95
+ setCookie("cookieBannerClosed", true, {
96
+ expires: expirationDate,
97
+ });
98
+ };
99
+ if (isStickyFooterPresent) marginBottom = 14;
100
+
101
+ // Calculate margin values in pixels for inline styles
102
+ const marginBottomPx = marginBottom * 4; // Tailwind: 3 = 12px, 14 = 56px (each unit is 4px)
103
+ const marginLeftPx = 12; // mx-3 = 12px
104
+
105
+ if (!isBannerVisible) return null;
106
+
107
+ return (
108
+ <div
109
+ id="cookie-banner"
110
+ ref={bannerRef}
111
+ className="fixed z-[1000] max-w-[350px] rounded-xl bg-white py-5 pl-6 pr-12 text-black shadow-lg ring-1 ring-gray-900/10 md:max-w-[656px]"
112
+ style={{
113
+ // maxWidth: maxWidth,
114
+ bottom: "0px",
115
+ right: "20px",
116
+ marginBottom: `${marginBottomPx}px`,
117
+ marginLeft: `${marginLeftPx}px`,
118
+ }}
119
+ aria-label="Cookie usage notification"
120
+ >
121
+ {" "}
122
+ {
123
+ <Button
124
+ showButtonAs="unstyled"
125
+ buttonClassName="absolute right-2 top-3 mr-2 mt-0 h-6 w-6 bg-white"
126
+ onClick={handleClose}
127
+ >
128
+ <MaterialIcon name="close" />
129
+ </Button>
130
+ }
131
+ {/* {content ? (
132
+ <div className="my-4 mx-auto [&>a]:footnote">
133
+ {renderContentfulRichText(
134
+ toDocument(content.richText),
135
+ false,
136
+ "footnote",
137
+ "footnote",
138
+ )}
139
+ </div>
140
+ ) : null} */}
141
+ {content.richText ? (
142
+ <div className="mx-auto my-4 [&>a]:footnote">{content.richText}</div>
143
+ ) : null}
144
+ </div>
145
+ );
146
+ }
@@ -1,7 +1,7 @@
1
- export interface TypeComponentRichTextFields {
2
- entryName?: string;
3
- anchorId?: string;
4
- title?: string;
5
- isTargetBlank?: boolean;
6
- richText: React.ReactNode;
7
- }
1
+ export interface TypeComponentRichTextFields {
2
+ entryName?: string;
3
+ anchorId?: string;
4
+ title?: string;
5
+ isTargetBlank?: boolean;
6
+ richText: React.ReactNode;
7
+ }
@@ -1,46 +1,46 @@
1
- import { CtaCallout } from "./index";
2
-
3
- import { DocsPage } from "@shared/stories/DocsTemplate";
4
- import type { Meta, StoryObj } from "@storybook/react";
5
-
6
- const meta: Meta<typeof CtaCallout> = {
7
- title: "Contentful Blocks/CtaCallout",
8
- component: CtaCallout,
9
- tags: ["autodocs"],
10
- parameters: {
11
- layout: "centered",
12
- docs: {
13
- page: DocsPage,
14
- description: {
15
- component:
16
- "Contentful CTA callout block with title, description, and optional button.",
17
- },
18
- },
19
- },
20
- args: {
21
- title: "Call to Action",
22
- subTitle: "Subtitle",
23
- description: "Description text.",
24
- background: "white",
25
- button: {
26
- showButtonAs: "solid",
27
- buttonVariant: "primary_brand",
28
- buttonLabel: "Learn More",
29
- buttonPrefix: "",
30
- badge: "",
31
- badgeIcon: "",
32
- buttonIcon: "",
33
- buttonIconPosition: "left",
34
- href: "",
35
- target: "_self",
36
- anchorId: "",
37
- preserveQueryParameters: false,
38
- clickToOpen: "",
39
- tabmodalNameToOpen: "",
40
- preDefinedFunctionExecution: "",
41
- },
42
- },
43
- };
44
- export default meta;
45
- type Story = StoryObj<typeof meta>;
46
- export const Default: Story = {};
1
+ import { CtaCallout } from "./index";
2
+
3
+ import { DocsPage } from "@shared/stories/DocsTemplate";
4
+ import type { Meta, StoryObj } from "@storybook/react";
5
+
6
+ const meta: Meta<typeof CtaCallout> = {
7
+ title: "Contentful Blocks/CtaCallout",
8
+ component: CtaCallout,
9
+ tags: ["autodocs"],
10
+ parameters: {
11
+ layout: "centered",
12
+ docs: {
13
+ page: DocsPage,
14
+ description: {
15
+ component:
16
+ "Contentful CTA callout block with title, description, and optional button.",
17
+ },
18
+ },
19
+ },
20
+ args: {
21
+ title: "Call to Action",
22
+ subTitle: "Subtitle",
23
+ description: "Description text.",
24
+ background: "white",
25
+ button: {
26
+ showButtonAs: "solid",
27
+ buttonVariant: "primary_brand",
28
+ buttonLabel: "Learn More",
29
+ buttonPrefix: "",
30
+ badge: "",
31
+ badgeIcon: "",
32
+ buttonIcon: "",
33
+ buttonIconPosition: "left",
34
+ href: "",
35
+ target: "_self",
36
+ anchorId: "",
37
+ preserveQueryParameters: false,
38
+ clickToOpen: "",
39
+ tabmodalNameToOpen: "",
40
+ preDefinedFunctionExecution: "",
41
+ },
42
+ },
43
+ };
44
+ export default meta;
45
+ type Story = StoryObj<typeof meta>;
46
+ export const Default: Story = {};