@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,260 @@
1
+ /// <reference types="@testing-library/jest-dom" />
2
+ import React from "react";
3
+ import { CallButton } from "./index";
4
+
5
+ import { render, screen } from "@testing-library/react";
6
+
7
+ // Mock dependencies
8
+ /* eslint-disable react/prop-types */
9
+ jest.mock("@shared/components/link", () => ({
10
+ Link: ({
11
+ children,
12
+ className,
13
+ href,
14
+ ...props
15
+ }: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
16
+ children: React.ReactNode;
17
+ }) => (
18
+ <a href={href} className={className} {...props}>
19
+ {children}
20
+ </a>
21
+ ),
22
+ }));
23
+ /* eslint-enable react/prop-types */
24
+
25
+ jest.mock("@shared/components/material-icon", () => ({
26
+ MaterialIcon: ({ name, className }: { name: string; className?: string }) => (
27
+ <span data-testid={`material-icon-${name}`} className={className}>
28
+ {name}
29
+ </span>
30
+ ),
31
+ }));
32
+
33
+ jest.mock("@shared/utils", () => ({
34
+ cx: (...args: unknown[]) => args.filter(Boolean).join(" "),
35
+ }));
36
+
37
+ describe("CallButton", () => {
38
+ describe("rendering", () => {
39
+ it("renders children text content", () => {
40
+ render(<CallButton href="tel:+1234567890">Call Us</CallButton>);
41
+ expect(screen.getByText("Call Us")).toBeInTheDocument();
42
+ });
43
+
44
+ it("renders the call icon", () => {
45
+ render(<CallButton href="tel:+1234567890">Call</CallButton>);
46
+ expect(screen.getByTestId("material-icon-call")).toBeInTheDocument();
47
+ });
48
+
49
+ it("renders an anchor element via Link", () => {
50
+ render(<CallButton href="tel:+1234567890">Call</CallButton>);
51
+ const link = screen.getByRole("link");
52
+ expect(link).toHaveAttribute("href", "tel:+1234567890");
53
+ });
54
+
55
+ it("applies displayName correctly", () => {
56
+ expect(CallButton.displayName).toBe("CallButton");
57
+ });
58
+ });
59
+
60
+ describe("size variants", () => {
61
+ it("applies sm size classes", () => {
62
+ render(
63
+ <CallButton href="tel:123" size="sm">
64
+ Call
65
+ </CallButton>
66
+ );
67
+ const link = screen.getByRole("link");
68
+ expect(link.className).toContain("h-6");
69
+ expect(link.className).toContain("text-xs");
70
+ });
71
+
72
+ it("applies md size classes by default", () => {
73
+ render(<CallButton href="tel:123">Call</CallButton>);
74
+ const link = screen.getByRole("link");
75
+ expect(link.className).toContain("h-8");
76
+ expect(link.className).toContain("text-sm");
77
+ });
78
+
79
+ it("applies lg size classes", () => {
80
+ render(
81
+ <CallButton href="tel:123" size="lg">
82
+ Call
83
+ </CallButton>
84
+ );
85
+ const link = screen.getByRole("link");
86
+ expect(link.className).toContain("h-10");
87
+ expect(link.className).toContain("text-base");
88
+ });
89
+ });
90
+
91
+ describe("buttonStyle variants", () => {
92
+ it("applies primary style classes by default", () => {
93
+ render(<CallButton href="tel:123">Call</CallButton>);
94
+ const link = screen.getByRole("link");
95
+ expect(link.className).toContain("border-border");
96
+ expect(link.className).toContain("text-text");
97
+ });
98
+
99
+ it("applies primary style classes when explicitly set", () => {
100
+ render(
101
+ <CallButton href="tel:123" buttonStyle="primary">
102
+ Call
103
+ </CallButton>
104
+ );
105
+ const link = screen.getByRole("link");
106
+ expect(link.className).toContain("border-border");
107
+ });
108
+ });
109
+
110
+ describe("blinking dot", () => {
111
+ it("does not render blinking dot by default", () => {
112
+ const { container } = render(
113
+ <CallButton href="tel:123">Call</CallButton>
114
+ );
115
+ const dot = container.querySelector(".animate-blink");
116
+ expect(dot).not.toBeInTheDocument();
117
+ });
118
+
119
+ it("renders blinking dot when showBlinkDot is true", () => {
120
+ const { container } = render(
121
+ <CallButton href="tel:123" showBlinkDot={true}>
122
+ Call
123
+ </CallButton>
124
+ );
125
+ const dot = container.querySelector(".animate-blink");
126
+ expect(dot).toBeInTheDocument();
127
+ });
128
+
129
+ it("applies correct dot size for sm", () => {
130
+ const { container } = render(
131
+ <CallButton href="tel:123" showBlinkDot={true} size="sm">
132
+ Call
133
+ </CallButton>
134
+ );
135
+ const dot = container.querySelector(".animate-blink");
136
+ expect(dot).toHaveClass("h-2", "w-2");
137
+ });
138
+
139
+ it("applies correct dot size for md", () => {
140
+ const { container } = render(
141
+ <CallButton href="tel:123" showBlinkDot={true} size="md">
142
+ Call
143
+ </CallButton>
144
+ );
145
+ const dot = container.querySelector(".animate-blink");
146
+ expect(dot).toHaveClass("h-3", "w-3");
147
+ });
148
+
149
+ it("applies correct dot size for lg", () => {
150
+ const { container } = render(
151
+ <CallButton href="tel:123" showBlinkDot={true} size="lg">
152
+ Call
153
+ </CallButton>
154
+ );
155
+ const dot = container.querySelector(".animate-blink");
156
+ expect(dot).toHaveClass("h-4", "w-4");
157
+ });
158
+ });
159
+
160
+ describe("prefix", () => {
161
+ it("renders without prefix wrapper when prefix is null", () => {
162
+ const { container } = render(
163
+ <CallButton href="tel:123" prefix={null}>
164
+ Call
165
+ </CallButton>
166
+ );
167
+ // Should not have the outer wrapper span
168
+ const wrapper = container.querySelector("span.flex.items-center.gap-2");
169
+ expect(wrapper).not.toBeInTheDocument();
170
+ });
171
+
172
+ it("renders without prefix wrapper when prefix is false", () => {
173
+ const { container } = render(
174
+ <CallButton href="tel:123" prefix={false}>
175
+ Call
176
+ </CallButton>
177
+ );
178
+ const wrapper = container.querySelector("span.flex.items-center.gap-2");
179
+ expect(wrapper).not.toBeInTheDocument();
180
+ });
181
+
182
+ it("renders without prefix wrapper when prefix is empty string", () => {
183
+ const { container } = render(
184
+ <CallButton href="tel:123" prefix="">
185
+ Call
186
+ </CallButton>
187
+ );
188
+ const wrapper = container.querySelector("span.flex.items-center.gap-2");
189
+ expect(wrapper).not.toBeInTheDocument();
190
+ });
191
+
192
+ it("renders string prefix in a span", () => {
193
+ render(
194
+ <CallButton href="tel:123" prefix="Call now:">
195
+ 555-1234
196
+ </CallButton>
197
+ );
198
+ expect(screen.getByText("Call now:")).toBeInTheDocument();
199
+ });
200
+
201
+ it("renders numeric prefix in a span", () => {
202
+ render(
203
+ <CallButton href="tel:123" prefix={42}>
204
+ Call
205
+ </CallButton>
206
+ );
207
+ expect(screen.getByText("42")).toBeInTheDocument();
208
+ });
209
+
210
+ it("renders ReactNode prefix directly", () => {
211
+ render(
212
+ <CallButton
213
+ href="tel:123"
214
+ prefix={<strong data-testid="custom-prefix">Custom</strong>}
215
+ >
216
+ Call
217
+ </CallButton>
218
+ );
219
+ expect(screen.getByTestId("custom-prefix")).toBeInTheDocument();
220
+ });
221
+
222
+ it("applies containerClassName to the outer wrapper", () => {
223
+ const { container } = render(
224
+ <CallButton
225
+ href="tel:123"
226
+ prefix="Prefix"
227
+ containerClassName="my-container"
228
+ >
229
+ Call
230
+ </CallButton>
231
+ );
232
+ const wrapper = container.firstChild as HTMLElement;
233
+ expect(wrapper.className).toContain("my-container");
234
+ });
235
+ });
236
+
237
+ describe("custom className", () => {
238
+ it("applies custom className to the link", () => {
239
+ render(
240
+ <CallButton href="tel:123" className="custom-class">
241
+ Call
242
+ </CallButton>
243
+ );
244
+ const link = screen.getByRole("link");
245
+ expect(link.className).toContain("custom-class");
246
+ });
247
+ });
248
+
249
+ describe("accessibility", () => {
250
+ it("passes aria attributes to the link", () => {
251
+ render(
252
+ <CallButton href="tel:123" aria-label="Call support">
253
+ Call
254
+ </CallButton>
255
+ );
256
+ const link = screen.getByRole("link");
257
+ expect(link).toHaveAttribute("aria-label", "Call support");
258
+ });
259
+ });
260
+ });
@@ -1,106 +1,106 @@
1
- import type { CallButtonProps } from "@shared/components/call-button/types";
2
- import { Link } from "@shared/components/link";
3
- import { MaterialIcon } from "@shared/components/material-icon";
4
- import type { IconProps } from "@shared/components/material-icon/types";
5
- import { cx } from "@shared/utils";
6
-
7
- export const CallButton = (props: CallButtonProps) => {
8
- const {
9
- showBlinkDot = false,
10
- buttonStyle = "primary",
11
- size = "md",
12
- children,
13
- className,
14
- prefix,
15
- containerClassName,
16
- ...rest
17
- } = props;
18
-
19
- const baseClasses =
20
- "relative flex items-center gap-2 font-medium tracking-wide outline-offset-4 rounded-full";
21
-
22
- const sizeClasses = {
23
- sm: "h-6 text-xs pl-1 pr-2",
24
- md: "h-8 text-sm pl-1 pr-2",
25
- lg: "h-10 text-base pl-2 pr-3",
26
- };
27
-
28
- const styleClasses = {
29
- primary: "border-[0.727px] border-border text-text",
30
- };
31
-
32
- const iconSize: Record<string, IconProps["size"]> = {
33
- sm: 20,
34
- md: 20,
35
- lg: 32,
36
- };
37
-
38
- const iconContainerSize = {
39
- sm: "h-5 w-5",
40
- md: "h-6 w-6",
41
- lg: "h-8 w-8",
42
- };
43
-
44
- const dotSize = {
45
- sm: "h-2 w-2",
46
- md: "h-3 w-3",
47
- lg: "h-4 w-4",
48
- };
49
-
50
- const dotPosition = {
51
- sm: "-left-4",
52
- md: "-left-6",
53
- lg: "-left-8",
54
- };
55
-
56
- const pill = (
57
- <Link
58
- {...rest}
59
- className={cx(
60
- baseClasses,
61
- sizeClasses[size],
62
- styleClasses[buttonStyle],
63
- className
64
- )}
65
- >
66
- {showBlinkDot ? (
67
- <span
68
- className={`pointer-events-none absolute animate-blink ${dotPosition[size]} top-1/2 ${dotSize[size]} -translate-y-1/2 rounded-full bg-icon-brand`}
69
- />
70
- ) : null}
71
- <span
72
- className={cx(
73
- "relative inline-flex items-center justify-center rounded-full bg-icon-brand",
74
- iconContainerSize[size]
75
- )}
76
- >
77
- <MaterialIcon
78
- name="call"
79
- fill={1}
80
- size={iconSize[size]}
81
- className="text-white"
82
- />
83
- </span>
84
- <span className="font-normal tracking-wide">{children}</span>
85
- </Link>
86
- );
87
-
88
- if (prefix == null || prefix === false || prefix === "") {
89
- return pill;
90
- }
91
-
92
- return (
93
- <span className={cx("flex items-center gap-2", containerClassName)}>
94
- {typeof prefix === "string" || typeof prefix === "number" ? (
95
- <span className="text-sm font-normal text-text">{prefix}</span>
96
- ) : (
97
- prefix
98
- )}
99
- {pill}
100
- </span>
101
- );
102
- };
103
-
104
- CallButton.displayName = "CallButton";
105
-
106
- export type { CallButtonProps };
1
+ import type { CallButtonProps } from "@shared/components/call-button/types";
2
+ import { Link } from "@shared/components/link";
3
+ import { MaterialIcon } from "@shared/components/material-icon";
4
+ import type { IconProps } from "@shared/components/material-icon/types";
5
+ import { cx } from "@shared/utils";
6
+
7
+ export const CallButton = (props: CallButtonProps) => {
8
+ const {
9
+ showBlinkDot = false,
10
+ buttonStyle = "primary",
11
+ size = "md",
12
+ children,
13
+ className,
14
+ prefix,
15
+ containerClassName,
16
+ ...rest
17
+ } = props;
18
+
19
+ const baseClasses =
20
+ "relative flex items-center gap-2 font-medium tracking-wide outline-offset-4 rounded-full";
21
+
22
+ const sizeClasses = {
23
+ sm: "h-6 text-xs pl-1 pr-2",
24
+ md: "h-8 text-sm pl-1 pr-2",
25
+ lg: "h-10 text-base pl-2 pr-3",
26
+ };
27
+
28
+ const styleClasses = {
29
+ primary: "border-[0.727px] border-border text-text",
30
+ };
31
+
32
+ const iconSize: Record<string, IconProps["size"]> = {
33
+ sm: 20,
34
+ md: 20,
35
+ lg: 32,
36
+ };
37
+
38
+ const iconContainerSize = {
39
+ sm: "h-5 w-5",
40
+ md: "h-6 w-6",
41
+ lg: "h-8 w-8",
42
+ };
43
+
44
+ const dotSize = {
45
+ sm: "h-2 w-2",
46
+ md: "h-3 w-3",
47
+ lg: "h-4 w-4",
48
+ };
49
+
50
+ const dotPosition = {
51
+ sm: "-left-4",
52
+ md: "-left-6",
53
+ lg: "-left-8",
54
+ };
55
+
56
+ const pill = (
57
+ <Link
58
+ {...rest}
59
+ className={cx(
60
+ baseClasses,
61
+ sizeClasses[size],
62
+ styleClasses[buttonStyle],
63
+ className
64
+ )}
65
+ >
66
+ {showBlinkDot ? (
67
+ <span
68
+ className={`pointer-events-none absolute animate-blink ${dotPosition[size]} top-1/2 ${dotSize[size]} -translate-y-1/2 rounded-full bg-icon-brand`}
69
+ />
70
+ ) : null}
71
+ <span
72
+ className={cx(
73
+ "relative inline-flex items-center justify-center rounded-full bg-icon-brand",
74
+ iconContainerSize[size]
75
+ )}
76
+ >
77
+ <MaterialIcon
78
+ name="call"
79
+ fill={1}
80
+ size={iconSize[size]}
81
+ className="text-white"
82
+ />
83
+ </span>
84
+ <span className="font-normal tracking-wide">{children}</span>
85
+ </Link>
86
+ );
87
+
88
+ if (prefix == null || prefix === false || prefix === "") {
89
+ return pill;
90
+ }
91
+
92
+ return (
93
+ <span className={cx("flex items-center gap-2", containerClassName)}>
94
+ {typeof prefix === "string" || typeof prefix === "number" ? (
95
+ <span className="text-sm font-normal text-text">{prefix}</span>
96
+ ) : (
97
+ prefix
98
+ )}
99
+ {pill}
100
+ </span>
101
+ );
102
+ };
103
+
104
+ CallButton.displayName = "CallButton";
105
+
106
+ export type { CallButtonProps };
@@ -1,16 +1,16 @@
1
- import { ReactNode } from "react";
2
-
3
- import { LinkProps } from "@shared/components/link/types";
4
-
5
- export interface CallButtonProps
6
- extends Omit<LinkProps, "children" | "prefix"> {
7
- showBlinkDot?: boolean;
8
- buttonStyle?: "primary";
9
- className?: string;
10
- size?: "sm" | "md" | "lg";
11
- children: ReactNode;
12
- /** Optional content rendered as a sibling BEFORE the pill (e.g. "Call now:"). */
13
- prefix?: ReactNode;
14
- /** Optional className applied to the outer wrapper when `prefix` is used. */
15
- containerClassName?: string;
16
- }
1
+ import { ReactNode } from "react";
2
+
3
+ import { LinkProps } from "@shared/components/link/types";
4
+
5
+ export interface CallButtonProps
6
+ extends Omit<LinkProps, "children" | "prefix"> {
7
+ showBlinkDot?: boolean;
8
+ buttonStyle?: "primary";
9
+ className?: string;
10
+ size?: "sm" | "md" | "lg";
11
+ children: ReactNode;
12
+ /** Optional content rendered as a sibling BEFORE the pill (e.g. "Call now:"). */
13
+ prefix?: ReactNode;
14
+ /** Optional className applied to the outer wrapper when `prefix` is used. */
15
+ containerClassName?: string;
16
+ }