@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,424 @@
1
+ import React from "react";
2
+ import { AnimationWrapper } from "./index";
3
+
4
+ import { render, screen } from "@testing-library/react";
5
+
6
+ import "@testing-library/jest-dom";
7
+
8
+ // Mock framer-motion
9
+ const mockUseReducedMotion = jest.fn(() => false);
10
+
11
+ jest.mock("framer-motion", () => {
12
+ const actual = jest.requireActual("framer-motion");
13
+ return {
14
+ ...actual,
15
+ useReducedMotion: () => mockUseReducedMotion(),
16
+ motion: new Proxy(
17
+ {},
18
+ {
19
+ get: (_target, prop: string) => {
20
+ const motionKeys = [
21
+ "whileHover",
22
+ "whileTap",
23
+ "transition",
24
+ "animate",
25
+ "initial",
26
+ "exit",
27
+ "variants",
28
+ ];
29
+ const MotionMock = React.forwardRef(
30
+ (props: Record<string, unknown>, ref: React.Ref<unknown>) => {
31
+ const rest: Record<string, unknown> = {};
32
+ Object.keys(props).forEach(key => {
33
+ if (!motionKeys.includes(key)) {
34
+ rest[key] = props[key];
35
+ }
36
+ });
37
+ return React.createElement(prop, {
38
+ ...rest,
39
+ ref,
40
+ "data-motion": "true",
41
+ "data-while-hover": JSON.stringify(props.whileHover),
42
+ "data-while-tap": JSON.stringify(props.whileTap),
43
+ "data-transition": JSON.stringify(props.transition),
44
+ });
45
+ }
46
+ );
47
+ MotionMock.displayName = `Motion(${prop})`;
48
+ return MotionMock;
49
+ },
50
+ }
51
+ ),
52
+ };
53
+ });
54
+
55
+ describe("AnimationWrapper", () => {
56
+ beforeEach(() => {
57
+ mockUseReducedMotion.mockReturnValue(false);
58
+ });
59
+
60
+ describe("when animation is disabled", () => {
61
+ it("returns the child directly when disableAnimation is true (default)", () => {
62
+ render(
63
+ <AnimationWrapper>
64
+ <button data-testid="child">Click</button>
65
+ </AnimationWrapper>
66
+ );
67
+ const btn = screen.getByTestId("child");
68
+ expect(btn).toBeInTheDocument();
69
+ expect(btn.tagName).toBe("BUTTON");
70
+ expect(btn).not.toHaveAttribute("data-motion");
71
+ });
72
+
73
+ it("returns the child directly when disableAnimation is explicitly true", () => {
74
+ render(
75
+ <AnimationWrapper disableAnimation={true}>
76
+ <span data-testid="child">Text</span>
77
+ </AnimationWrapper>
78
+ );
79
+ expect(screen.getByTestId("child")).not.toHaveAttribute("data-motion");
80
+ });
81
+
82
+ it("returns the child directly when user prefers reduced motion", () => {
83
+ mockUseReducedMotion.mockReturnValue(true);
84
+ render(
85
+ <AnimationWrapper disableAnimation={false} animationType="scale">
86
+ <div data-testid="child">Content</div>
87
+ </AnimationWrapper>
88
+ );
89
+ expect(screen.getByTestId("child")).not.toHaveAttribute("data-motion");
90
+ });
91
+ });
92
+
93
+ describe("when animation is enabled", () => {
94
+ describe("with intrinsic elements", () => {
95
+ it("wraps intrinsic element with motion component for scale animation", () => {
96
+ render(
97
+ <AnimationWrapper disableAnimation={false} animationType="scale">
98
+ <div data-testid="child">Animated</div>
99
+ </AnimationWrapper>
100
+ );
101
+ const el = screen.getByTestId("child");
102
+ expect(el).toHaveAttribute("data-motion", "true");
103
+ const whileHover = JSON.parse(
104
+ el.getAttribute("data-while-hover") || "{}"
105
+ );
106
+ expect(whileHover).toEqual({ scale: 1.02 });
107
+ const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
108
+ expect(whileTap).toEqual({ scale: 0.98 });
109
+ });
110
+
111
+ it("applies shadow animation preset", () => {
112
+ render(
113
+ <AnimationWrapper disableAnimation={false} animationType="shadow">
114
+ <div data-testid="child">Shadow</div>
115
+ </AnimationWrapper>
116
+ );
117
+ const el = screen.getByTestId("child");
118
+ const whileHover = JSON.parse(
119
+ el.getAttribute("data-while-hover") || "{}"
120
+ );
121
+ expect(whileHover).toEqual({
122
+ boxShadow: "0 10px 25px rgba(0,0,0,0.15)",
123
+ });
124
+ // shadow preset has no whileTap, so mergedWhileTap is undefined
125
+ const whileTap = el.getAttribute("data-while-tap");
126
+ expect(whileTap).toBeNull();
127
+ });
128
+
129
+ it("applies lift animation preset", () => {
130
+ render(
131
+ <AnimationWrapper disableAnimation={false} animationType="lift">
132
+ <div data-testid="child">Lift</div>
133
+ </AnimationWrapper>
134
+ );
135
+ const el = screen.getByTestId("child");
136
+ const whileHover = JSON.parse(
137
+ el.getAttribute("data-while-hover") || "{}"
138
+ );
139
+ expect(whileHover).toEqual({ y: -5 });
140
+ const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
141
+ expect(whileTap).toEqual({ y: 0 });
142
+ });
143
+
144
+ it("applies opacity animation preset", () => {
145
+ render(
146
+ <AnimationWrapper disableAnimation={false} animationType="opacity">
147
+ <div data-testid="child">Opacity</div>
148
+ </AnimationWrapper>
149
+ );
150
+ const el = screen.getByTestId("child");
151
+ const whileHover = JSON.parse(
152
+ el.getAttribute("data-while-hover") || "{}"
153
+ );
154
+ expect(whileHover).toEqual({ opacity: 0.8 });
155
+ const transition = JSON.parse(
156
+ el.getAttribute("data-transition") || "{}"
157
+ );
158
+ expect(transition).toEqual({ duration: 0.2 });
159
+ });
160
+
161
+ it("applies grow animation preset", () => {
162
+ render(
163
+ <AnimationWrapper disableAnimation={false} animationType="grow">
164
+ <div data-testid="child">Grow</div>
165
+ </AnimationWrapper>
166
+ );
167
+ const el = screen.getByTestId("child");
168
+ const whileHover = JSON.parse(
169
+ el.getAttribute("data-while-hover") || "{}"
170
+ );
171
+ expect(whileHover).toEqual({ scale: 1.1 });
172
+ const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
173
+ expect(whileTap).toEqual({ scale: 0.95 });
174
+ const transition = JSON.parse(
175
+ el.getAttribute("data-transition") || "{}"
176
+ );
177
+ expect(transition).toEqual({
178
+ type: "spring",
179
+ stiffness: 300,
180
+ damping: 20,
181
+ });
182
+ });
183
+
184
+ it("works with different intrinsic HTML elements (span)", () => {
185
+ render(
186
+ <AnimationWrapper disableAnimation={false} animationType="scale">
187
+ <span data-testid="child">Span</span>
188
+ </AnimationWrapper>
189
+ );
190
+ const el = screen.getByTestId("child");
191
+ expect(el.tagName).toBe("SPAN");
192
+ expect(el).toHaveAttribute("data-motion", "true");
193
+ });
194
+
195
+ it("works with anchor elements", () => {
196
+ render(
197
+ <AnimationWrapper disableAnimation={false} animationType="lift">
198
+ <a data-testid="child" href="/test">
199
+ Link
200
+ </a>
201
+ </AnimationWrapper>
202
+ );
203
+ const el = screen.getByTestId("child");
204
+ expect(el.tagName).toBe("A");
205
+ expect(el).toHaveAttribute("href", "/test");
206
+ expect(el).toHaveAttribute("data-motion", "true");
207
+ });
208
+ });
209
+
210
+ describe("with composite (non-intrinsic) elements", () => {
211
+ const CustomComponent = ({ label }: { label: string }) => (
212
+ <span data-testid="custom">{label}</span>
213
+ );
214
+
215
+ it("wraps composite component in motion.div", () => {
216
+ const { container } = render(
217
+ <AnimationWrapper disableAnimation={false} animationType="scale">
218
+ <CustomComponent label="Hello" />
219
+ </AnimationWrapper>
220
+ );
221
+ // The wrapper should be a div with motion attributes
222
+ const wrapper = container.firstElementChild;
223
+ expect(wrapper?.tagName).toBe("DIV");
224
+ expect(wrapper).toHaveAttribute("data-motion", "true");
225
+ // The child should be rendered inside
226
+ expect(screen.getByTestId("custom")).toBeInTheDocument();
227
+ expect(screen.getByText("Hello")).toBeInTheDocument();
228
+ });
229
+
230
+ it("applies animation presets to motion.div wrapper", () => {
231
+ const { container } = render(
232
+ <AnimationWrapper disableAnimation={false} animationType="grow">
233
+ <CustomComponent label="Test" />
234
+ </AnimationWrapper>
235
+ );
236
+ const wrapper = container.firstElementChild;
237
+ const whileHover = JSON.parse(
238
+ wrapper?.getAttribute("data-while-hover") || "{}"
239
+ );
240
+ expect(whileHover).toEqual({ scale: 1.1 });
241
+ });
242
+ });
243
+
244
+ describe("with multiple animation types", () => {
245
+ it("merges multiple animation presets", () => {
246
+ render(
247
+ <AnimationWrapper
248
+ disableAnimation={false}
249
+ animationType={["scale", "shadow"]}
250
+ >
251
+ <div data-testid="child">Multi</div>
252
+ </AnimationWrapper>
253
+ );
254
+ const el = screen.getByTestId("child");
255
+ const whileHover = JSON.parse(
256
+ el.getAttribute("data-while-hover") || "{}"
257
+ );
258
+ expect(whileHover).toEqual({
259
+ scale: 1.02,
260
+ boxShadow: "0 10px 25px rgba(0,0,0,0.15)",
261
+ });
262
+ });
263
+
264
+ it("merges whileTap from multiple presets", () => {
265
+ render(
266
+ <AnimationWrapper
267
+ disableAnimation={false}
268
+ animationType={["scale", "lift"]}
269
+ >
270
+ <div data-testid="child">Multi Tap</div>
271
+ </AnimationWrapper>
272
+ );
273
+ const el = screen.getByTestId("child");
274
+ const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
275
+ expect(whileTap).toEqual({ scale: 0.98, y: 0 });
276
+ });
277
+
278
+ it("merges transitions from multiple presets", () => {
279
+ render(
280
+ <AnimationWrapper
281
+ disableAnimation={false}
282
+ animationType={["opacity", "lift"]}
283
+ >
284
+ <div data-testid="child">Merged</div>
285
+ </AnimationWrapper>
286
+ );
287
+ const el = screen.getByTestId("child");
288
+ const transition = JSON.parse(
289
+ el.getAttribute("data-transition") || "{}"
290
+ );
291
+ expect(transition).toEqual({
292
+ duration: 0.2,
293
+ type: "spring",
294
+ stiffness: 300,
295
+ damping: 20,
296
+ });
297
+ });
298
+
299
+ it("handles empty array of animation types", () => {
300
+ render(
301
+ <AnimationWrapper disableAnimation={false} animationType={[]}>
302
+ <div data-testid="child">No Animation</div>
303
+ </AnimationWrapper>
304
+ );
305
+ const el = screen.getByTestId("child");
306
+ // With empty array, mergePresets returns undefined, so no preset is applied
307
+ expect(el).toHaveAttribute("data-motion", "true");
308
+ });
309
+ });
310
+
311
+ describe("custom overrides", () => {
312
+ it("overrides preset whileHover with custom prop", () => {
313
+ render(
314
+ <AnimationWrapper
315
+ disableAnimation={false}
316
+ animationType="scale"
317
+ whileHover={{ scale: 2 }}
318
+ >
319
+ <div data-testid="child">Override</div>
320
+ </AnimationWrapper>
321
+ );
322
+ const el = screen.getByTestId("child");
323
+ const whileHover = JSON.parse(
324
+ el.getAttribute("data-while-hover") || "{}"
325
+ );
326
+ // Custom override should merge with preset (custom wins)
327
+ expect(whileHover.scale).toBe(2);
328
+ });
329
+
330
+ it("overrides preset whileTap with custom prop", () => {
331
+ render(
332
+ <AnimationWrapper
333
+ disableAnimation={false}
334
+ animationType="scale"
335
+ whileTap={{ scale: 0.5 }}
336
+ >
337
+ <div data-testid="child">Override Tap</div>
338
+ </AnimationWrapper>
339
+ );
340
+ const el = screen.getByTestId("child");
341
+ const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
342
+ expect(whileTap.scale).toBe(0.5);
343
+ });
344
+
345
+ it("overrides preset transition with custom prop", () => {
346
+ render(
347
+ <AnimationWrapper
348
+ disableAnimation={false}
349
+ animationType="scale"
350
+ transition={{ duration: 1 }}
351
+ >
352
+ <div data-testid="child">Override Trans</div>
353
+ </AnimationWrapper>
354
+ );
355
+ const el = screen.getByTestId("child");
356
+ const transition = JSON.parse(
357
+ el.getAttribute("data-transition") || "{}"
358
+ );
359
+ expect(transition.duration).toBe(1);
360
+ });
361
+
362
+ it("uses custom whileHover even without animationType", () => {
363
+ render(
364
+ <AnimationWrapper
365
+ disableAnimation={false}
366
+ whileHover={{ rotate: 10 }}
367
+ >
368
+ <div data-testid="child">Custom Only</div>
369
+ </AnimationWrapper>
370
+ );
371
+ const el = screen.getByTestId("child");
372
+ const whileHover = JSON.parse(
373
+ el.getAttribute("data-while-hover") || "{}"
374
+ );
375
+ expect(whileHover).toEqual({ rotate: 10 });
376
+ });
377
+
378
+ it("uses custom whileTap without animationType", () => {
379
+ render(
380
+ <AnimationWrapper disableAnimation={false} whileTap={{ rotate: -5 }}>
381
+ <div data-testid="child">Tap</div>
382
+ </AnimationWrapper>
383
+ );
384
+ const el = screen.getByTestId("child");
385
+ const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
386
+ expect(whileTap).toEqual({ rotate: -5 });
387
+ });
388
+
389
+ it("uses custom transition without animationType", () => {
390
+ render(
391
+ <AnimationWrapper
392
+ disableAnimation={false}
393
+ transition={{ delay: 0.5 }}
394
+ >
395
+ <div data-testid="child">Trans</div>
396
+ </AnimationWrapper>
397
+ );
398
+ const el = screen.getByTestId("child");
399
+ const transition = JSON.parse(
400
+ el.getAttribute("data-transition") || "{}"
401
+ );
402
+ expect(transition).toEqual({ delay: 0.5 });
403
+ });
404
+ });
405
+
406
+ describe("no animationType and no custom overrides", () => {
407
+ it("renders motion component with undefined animation props", () => {
408
+ render(
409
+ <AnimationWrapper disableAnimation={false}>
410
+ <div data-testid="child">Plain</div>
411
+ </AnimationWrapper>
412
+ );
413
+ const el = screen.getByTestId("child");
414
+ expect(el).toHaveAttribute("data-motion", "true");
415
+ });
416
+ });
417
+ });
418
+
419
+ describe("displayName", () => {
420
+ it("has correct displayName", () => {
421
+ expect(AnimationWrapper.displayName).toBe("AnimationWrapper");
422
+ });
423
+ });
424
+ });
@@ -1,129 +1,129 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { AnimationType, AnimationWrapperProps } from "./types";
5
- import { motion, useReducedMotion } from "framer-motion";
6
-
7
- interface AnimationPreset {
8
- whileHover: Record<string, unknown>;
9
- whileTap?: Record<string, unknown>;
10
- transition: Record<string, unknown>;
11
- }
12
-
13
- const ANIMATION_PRESETS: Record<AnimationType, AnimationPreset> = {
14
- scale: {
15
- whileHover: { scale: 1.02 },
16
- whileTap: { scale: 0.98 },
17
- transition: { duration: 0.3 },
18
- },
19
- shadow: {
20
- whileHover: { boxShadow: "0 10px 25px rgba(0,0,0,0.15)" },
21
- transition: { duration: 0.3 },
22
- },
23
- lift: {
24
- whileHover: { y: -5 },
25
- whileTap: { y: 0 },
26
- transition: { type: "spring", stiffness: 300, damping: 20 },
27
- },
28
- opacity: {
29
- whileHover: { opacity: 0.8 },
30
- transition: { duration: 0.2 },
31
- },
32
- grow: {
33
- whileHover: { scale: 1.1 },
34
- whileTap: { scale: 0.95 },
35
- transition: { type: "spring", stiffness: 300, damping: 20 },
36
- },
37
- };
38
-
39
- function mergePresets(animationType?: AnimationType | AnimationType[]) {
40
- if (!animationType) return undefined;
41
-
42
- const types = Array.isArray(animationType) ? animationType : [animationType];
43
- if (types.length === 0) return undefined;
44
-
45
- return types.reduce<AnimationPreset>(
46
- (merged, type) => {
47
- const preset = ANIMATION_PRESETS[type];
48
- return {
49
- whileHover: { ...merged.whileHover, ...preset.whileHover },
50
- ...(merged.whileTap || preset.whileTap
51
- ? {
52
- whileTap: { ...merged.whileTap, ...preset.whileTap },
53
- }
54
- : {}),
55
- transition: { ...merged.transition, ...preset.transition },
56
- };
57
- },
58
- { whileHover: {}, transition: {} }
59
- );
60
- }
61
-
62
- export function AnimationWrapper({
63
- children,
64
- animationType,
65
- // default to true for backward compatibility
66
- disableAnimation = true,
67
- whileHover,
68
- whileTap,
69
- transition,
70
- ...motionProps
71
- }: AnimationWrapperProps) {
72
- const prefersReducedMotion = useReducedMotion();
73
-
74
- const child = React.Children.only(children) as React.ReactElement<
75
- Record<string, unknown>
76
- >;
77
-
78
- if (disableAnimation || prefersReducedMotion) {
79
- return child;
80
- }
81
-
82
- const preset = mergePresets(animationType);
83
-
84
- const mergedWhileHover = preset
85
- ? { ...preset.whileHover, ...(whileHover as Record<string, unknown>) }
86
- : whileHover;
87
-
88
- const mergedWhileTap =
89
- preset?.whileTap || whileTap
90
- ? { ...preset?.whileTap, ...(whileTap as Record<string, unknown>) }
91
- : undefined;
92
-
93
- const mergedTransition = preset
94
- ? { ...preset.transition, ...(transition as Record<string, unknown>) }
95
- : transition;
96
-
97
- const isIntrinsicElement = typeof child.type === "string";
98
-
99
- if (isIntrinsicElement) {
100
- const MotionComponent = motion[
101
- child.type as keyof typeof motion
102
- ] as React.ComponentType<Record<string, unknown>>;
103
-
104
- return (
105
- <MotionComponent
106
- {...child.props}
107
- whileHover={mergedWhileHover}
108
- whileTap={mergedWhileTap}
109
- transition={mergedTransition}
110
- {...motionProps}
111
- />
112
- );
113
- }
114
-
115
- return (
116
- <motion.div
117
- whileHover={mergedWhileHover}
118
- whileTap={mergedWhileTap}
119
- transition={mergedTransition}
120
- {...motionProps}
121
- >
122
- {child}
123
- </motion.div>
124
- );
125
- }
126
-
127
- AnimationWrapper.displayName = "AnimationWrapper";
128
-
129
- export type { AnimationWrapperProps, AnimationType };
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { AnimationType, AnimationWrapperProps } from "./types";
5
+ import { motion, useReducedMotion } from "framer-motion";
6
+
7
+ interface AnimationPreset {
8
+ whileHover: Record<string, unknown>;
9
+ whileTap?: Record<string, unknown>;
10
+ transition: Record<string, unknown>;
11
+ }
12
+
13
+ const ANIMATION_PRESETS: Record<AnimationType, AnimationPreset> = {
14
+ scale: {
15
+ whileHover: { scale: 1.02 },
16
+ whileTap: { scale: 0.98 },
17
+ transition: { duration: 0.3 },
18
+ },
19
+ shadow: {
20
+ whileHover: { boxShadow: "0 10px 25px rgba(0,0,0,0.15)" },
21
+ transition: { duration: 0.3 },
22
+ },
23
+ lift: {
24
+ whileHover: { y: -5 },
25
+ whileTap: { y: 0 },
26
+ transition: { type: "spring", stiffness: 300, damping: 20 },
27
+ },
28
+ opacity: {
29
+ whileHover: { opacity: 0.8 },
30
+ transition: { duration: 0.2 },
31
+ },
32
+ grow: {
33
+ whileHover: { scale: 1.1 },
34
+ whileTap: { scale: 0.95 },
35
+ transition: { type: "spring", stiffness: 300, damping: 20 },
36
+ },
37
+ };
38
+
39
+ function mergePresets(animationType?: AnimationType | AnimationType[]) {
40
+ if (!animationType) return undefined;
41
+
42
+ const types = Array.isArray(animationType) ? animationType : [animationType];
43
+ if (types.length === 0) return undefined;
44
+
45
+ return types.reduce<AnimationPreset>(
46
+ (merged, type) => {
47
+ const preset = ANIMATION_PRESETS[type];
48
+ return {
49
+ whileHover: { ...merged.whileHover, ...preset.whileHover },
50
+ ...(merged.whileTap || preset.whileTap
51
+ ? {
52
+ whileTap: { ...merged.whileTap, ...preset.whileTap },
53
+ }
54
+ : {}),
55
+ transition: { ...merged.transition, ...preset.transition },
56
+ };
57
+ },
58
+ { whileHover: {}, transition: {} }
59
+ );
60
+ }
61
+
62
+ export function AnimationWrapper({
63
+ children,
64
+ animationType,
65
+ // default to true for backward compatibility
66
+ disableAnimation = true,
67
+ whileHover,
68
+ whileTap,
69
+ transition,
70
+ ...motionProps
71
+ }: AnimationWrapperProps) {
72
+ const prefersReducedMotion = useReducedMotion();
73
+
74
+ const child = React.Children.only(children) as React.ReactElement<
75
+ Record<string, unknown>
76
+ >;
77
+
78
+ if (disableAnimation || prefersReducedMotion) {
79
+ return child;
80
+ }
81
+
82
+ const preset = mergePresets(animationType);
83
+
84
+ const mergedWhileHover = preset
85
+ ? { ...preset.whileHover, ...(whileHover as Record<string, unknown>) }
86
+ : whileHover;
87
+
88
+ const mergedWhileTap =
89
+ preset?.whileTap || whileTap
90
+ ? { ...preset?.whileTap, ...(whileTap as Record<string, unknown>) }
91
+ : undefined;
92
+
93
+ const mergedTransition = preset
94
+ ? { ...preset.transition, ...(transition as Record<string, unknown>) }
95
+ : transition;
96
+
97
+ const isIntrinsicElement = typeof child.type === "string";
98
+
99
+ if (isIntrinsicElement) {
100
+ const MotionComponent = motion[
101
+ child.type as keyof typeof motion
102
+ ] as React.ComponentType<Record<string, unknown>>;
103
+
104
+ return (
105
+ <MotionComponent
106
+ {...child.props}
107
+ whileHover={mergedWhileHover}
108
+ whileTap={mergedWhileTap}
109
+ transition={mergedTransition}
110
+ {...motionProps}
111
+ />
112
+ );
113
+ }
114
+
115
+ return (
116
+ <motion.div
117
+ whileHover={mergedWhileHover}
118
+ whileTap={mergedWhileTap}
119
+ transition={mergedTransition}
120
+ {...motionProps}
121
+ >
122
+ {child}
123
+ </motion.div>
124
+ );
125
+ }
126
+
127
+ AnimationWrapper.displayName = "AnimationWrapper";
128
+
129
+ export type { AnimationWrapperProps, AnimationType };