@windstream/react-shared-components 0.1.81 → 0.1.87

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 (203) hide show
  1. package/README.md +635 -635
  2. package/dist/contentful/index.d.ts +5 -0
  3. package/dist/contentful/index.esm.js +3 -3
  4. package/dist/contentful/index.esm.js.map +1 -1
  5. package/dist/contentful/index.js +2 -2
  6. package/dist/contentful/index.js.map +1 -1
  7. package/dist/core.d.ts +17 -4
  8. package/dist/index.d.ts +18 -5
  9. package/dist/index.esm.js +6 -6
  10. package/dist/index.esm.js.map +1 -1
  11. package/dist/index.js +6 -6
  12. package/dist/index.js.map +1 -1
  13. package/package.json +185 -185
  14. package/src/components/accordion/Accordion.stories.tsx +230 -230
  15. package/src/components/accordion/index.tsx +70 -70
  16. package/src/components/accordion/types.ts +12 -12
  17. package/src/components/alert-card/AlertCard.stories.tsx +171 -171
  18. package/src/components/alert-card/index.tsx +41 -41
  19. package/src/components/alert-card/types.ts +13 -13
  20. package/src/components/animation-wrapper/index.tsx +129 -0
  21. package/src/components/animation-wrapper/types.ts +11 -0
  22. package/src/components/brand-button/BrandButton.stories.tsx +223 -223
  23. package/src/components/brand-button/helpers.ts +35 -35
  24. package/src/components/brand-button/index.tsx +120 -120
  25. package/src/components/brand-button/types.ts +38 -38
  26. package/src/components/button/Button.stories.tsx +108 -108
  27. package/src/components/button/index.tsx +27 -27
  28. package/src/components/button/types.ts +14 -14
  29. package/src/components/call-button/CallButton.stories.tsx +324 -324
  30. package/src/components/call-button/index.tsx +106 -106
  31. package/src/components/call-button/types.ts +16 -16
  32. package/src/components/checkbox/Checkbox.stories.tsx +247 -247
  33. package/src/components/checkbox/index.tsx +197 -197
  34. package/src/components/checkbox/types.ts +27 -27
  35. package/src/components/checklist/Checklist.stories.tsx +150 -150
  36. package/src/components/checklist/index.tsx +61 -61
  37. package/src/components/checklist/types.ts +17 -17
  38. package/src/components/collapse/Collapse.stories.tsx +255 -255
  39. package/src/components/collapse/index.tsx +46 -46
  40. package/src/components/collapse/types.ts +6 -6
  41. package/src/components/divider/Divider.stories.tsx +205 -205
  42. package/src/components/divider/index.tsx +22 -22
  43. package/src/components/divider/type.ts +3 -3
  44. package/src/components/image/Image.stories.tsx +113 -113
  45. package/src/components/image/index.tsx +25 -25
  46. package/src/components/image/types.ts +40 -40
  47. package/src/components/input/Input.stories.tsx +325 -325
  48. package/src/components/input/index.tsx +177 -177
  49. package/src/components/input/types.ts +37 -37
  50. package/src/components/link/Link.stories.tsx +163 -163
  51. package/src/components/link/index.tsx +116 -116
  52. package/src/components/link/types.ts +25 -25
  53. package/src/components/list/List.stories.tsx +272 -272
  54. package/src/components/list/index.tsx +88 -88
  55. package/src/components/list/list-item/index.tsx +38 -38
  56. package/src/components/list/list-item/types.ts +13 -13
  57. package/src/components/list/types.ts +29 -29
  58. package/src/components/material-icon/MaterialIcon.stories.tsx +322 -322
  59. package/src/components/material-icon/constants.ts +99 -99
  60. package/src/components/material-icon/index.tsx +47 -47
  61. package/src/components/material-icon/types.ts +31 -31
  62. package/src/components/modal/Modal.stories.tsx +171 -171
  63. package/src/components/modal/index.tsx +164 -164
  64. package/src/components/modal/types.ts +24 -24
  65. package/src/components/next-image/index.tsx +74 -74
  66. package/src/components/next-image/types.ts +1 -1
  67. package/src/components/pagination/index.tsx +91 -91
  68. package/src/components/pagination/types.ts +6 -6
  69. package/src/components/radio-button/RadioButton.stories.tsx +307 -307
  70. package/src/components/radio-button/index.tsx +75 -75
  71. package/src/components/radio-button/types.ts +21 -21
  72. package/src/components/see-more/SeeMore.stories.tsx +181 -181
  73. package/src/components/see-more/index.tsx +44 -44
  74. package/src/components/see-more/types.ts +4 -4
  75. package/src/components/select/Select.stories.tsx +411 -411
  76. package/src/components/select/index.tsx +155 -155
  77. package/src/components/select/types.ts +36 -36
  78. package/src/components/select-plan-button/SelectPlanButton.stories.tsx +184 -184
  79. package/src/components/select-plan-button/index.tsx +63 -63
  80. package/src/components/select-plan-button/types.ts +17 -17
  81. package/src/components/skeleton/Skeleton.stories.tsx +179 -179
  82. package/src/components/skeleton/index.tsx +61 -61
  83. package/src/components/skeleton/types.ts +4 -4
  84. package/src/components/spinner/Spinner.stories.tsx +335 -335
  85. package/src/components/spinner/index.tsx +44 -44
  86. package/src/components/spinner/types.ts +5 -5
  87. package/src/components/text/Text.stories.tsx +321 -321
  88. package/src/components/text/index.tsx +25 -25
  89. package/src/components/text/types.ts +45 -45
  90. package/src/components/tooltip/Tooltip.stories.tsx +219 -219
  91. package/src/components/tooltip/index.tsx +74 -74
  92. package/src/components/tooltip/types.ts +7 -7
  93. package/src/components/view-cart-button/ViewCartButton.stories.tsx +252 -252
  94. package/src/components/view-cart-button/index.tsx +42 -42
  95. package/src/components/view-cart-button/types.ts +5 -5
  96. package/src/contentful/blocks/accordion/Accordion.stories.mocks.tsx +128 -128
  97. package/src/contentful/blocks/accordion/Accordion.stories.tsx +98 -98
  98. package/src/contentful/blocks/accordion/index.tsx +112 -112
  99. package/src/contentful/blocks/accordion/types.ts +34 -34
  100. package/src/contentful/blocks/address-input-banner/index.tsx +52 -52
  101. package/src/contentful/blocks/address-input-banner/types.ts +14 -14
  102. package/src/contentful/blocks/anchored-bottom-banner/index.tsx +181 -181
  103. package/src/contentful/blocks/anchored-bottom-banner/types.ts +13 -13
  104. package/src/contentful/blocks/blogs-grid/BlogGrid.stories.mocks.tsx +144 -144
  105. package/src/contentful/blocks/blogs-grid/BlogGrid.stories.tsx +156 -156
  106. package/src/contentful/blocks/blogs-grid/index.tsx +134 -134
  107. package/src/contentful/blocks/blogs-grid/types.ts +26 -26
  108. package/src/contentful/blocks/blogs-grid-base/index.tsx +119 -119
  109. package/src/contentful/blocks/blogs-grid-base/types.ts +36 -36
  110. package/src/contentful/blocks/breadcrumbs/BreadcrumbNavigation.stories.tsx +147 -147
  111. package/src/contentful/blocks/breadcrumbs/index.tsx +95 -95
  112. package/src/contentful/blocks/breadcrumbs/types.ts +8 -8
  113. package/src/contentful/blocks/button/Button.stories.tsx +40 -40
  114. package/src/contentful/blocks/button/index.tsx +131 -131
  115. package/src/contentful/blocks/button/types.ts +39 -39
  116. package/src/contentful/blocks/callout/Callout.stories.tsx +23 -23
  117. package/src/contentful/blocks/callout/index.tsx +279 -265
  118. package/src/contentful/blocks/callout/types.ts +78 -72
  119. package/src/contentful/blocks/cards/Cards.stories.tsx +23 -23
  120. package/src/contentful/blocks/cards/blog-card/index.tsx +129 -129
  121. package/src/contentful/blocks/cards/blog-card/types.ts +34 -34
  122. package/src/contentful/blocks/cards/floating-image-card/index.tsx +119 -119
  123. package/src/contentful/blocks/cards/floating-image-card/types.ts +30 -30
  124. package/src/contentful/blocks/cards/full-image-card/index.tsx +130 -130
  125. package/src/contentful/blocks/cards/full-image-card/types.ts +29 -29
  126. package/src/contentful/blocks/cards/index.tsx +13 -13
  127. package/src/contentful/blocks/cards/product-card/index.tsx +251 -251
  128. package/src/contentful/blocks/cards/product-card/types.ts +28 -28
  129. package/src/contentful/blocks/cards/simple-card/index.tsx +325 -325
  130. package/src/contentful/blocks/cards/simple-card/types.ts +71 -71
  131. package/src/contentful/blocks/cards/testimonial-card/index.tsx +90 -90
  132. package/src/contentful/blocks/cards/testimonial-card/types.tsx +12 -12
  133. package/src/contentful/blocks/cards/types.ts +1 -1
  134. package/src/contentful/blocks/carousel/Carousel.stories.tsx +23 -23
  135. package/src/contentful/blocks/carousel/helper.tsx +494 -494
  136. package/src/contentful/blocks/carousel/index.tsx +87 -87
  137. package/src/contentful/blocks/carousel/types.ts +145 -145
  138. package/src/contentful/blocks/cart-retention-banner/index.tsx +105 -105
  139. package/src/contentful/blocks/cart-retention-banner/types.ts +11 -11
  140. package/src/contentful/blocks/comparison-table/index.tsx +29 -29
  141. package/src/contentful/blocks/comparison-table/types.ts +6 -6
  142. package/src/contentful/blocks/cookiebanner/index.tsx +146 -146
  143. package/src/contentful/blocks/cookiebanner/type.ts +7 -7
  144. package/src/contentful/blocks/cta-callout/CtaCallout.stories.tsx +46 -46
  145. package/src/contentful/blocks/cta-callout/index.tsx +71 -71
  146. package/src/contentful/blocks/cta-callout/types.ts +26 -26
  147. package/src/contentful/blocks/dynamic-tabs/index.tsx +204 -204
  148. package/src/contentful/blocks/dynamic-tabs/types.ts +21 -21
  149. package/src/contentful/blocks/email-input-block/index.tsx +116 -116
  150. package/src/contentful/blocks/email-input-block/types.ts +16 -16
  151. package/src/contentful/blocks/find-kinetic/FindKinetic.stories.tsx +23 -23
  152. package/src/contentful/blocks/find-kinetic/index.tsx +130 -130
  153. package/src/contentful/blocks/find-kinetic/types.ts +19 -19
  154. package/src/contentful/blocks/floating-banner/FloatingBanner.stories.tsx +34 -34
  155. package/src/contentful/blocks/floating-banner/index.tsx +97 -97
  156. package/src/contentful/blocks/floating-banner/types.ts +22 -22
  157. package/src/contentful/blocks/footer/Footer.stories.tsx +317 -317
  158. package/src/contentful/blocks/footer/index.tsx +91 -91
  159. package/src/contentful/blocks/footer/types.ts +13 -13
  160. package/src/contentful/blocks/image-promo-bar/ImagePromoBar.stories.tsx +23 -23
  161. package/src/contentful/blocks/image-promo-bar/helper.tsx +28 -28
  162. package/src/contentful/blocks/image-promo-bar/index.tsx +246 -246
  163. package/src/contentful/blocks/image-promo-bar/types.ts +44 -44
  164. package/src/contentful/blocks/image-promo-bar/vimeo-embed.tsx +93 -93
  165. package/src/contentful/blocks/image-promo-bar/youtube-embed.tsx +46 -46
  166. package/src/contentful/blocks/modal/constants.ts +53 -53
  167. package/src/contentful/blocks/modal/index.tsx +107 -107
  168. package/src/contentful/blocks/modal/types.ts +12 -12
  169. package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.tsx +139 -139
  170. package/src/contentful/blocks/navigation/index.tsx +568 -568
  171. package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.tsx +82 -82
  172. package/src/contentful/blocks/navigation/types.ts +71 -71
  173. package/src/contentful/blocks/primary-hero/PrimaryHero.stories.tsx +23 -23
  174. package/src/contentful/blocks/primary-hero/index.tsx +236 -236
  175. package/src/contentful/blocks/primary-hero/types.ts +37 -37
  176. package/src/contentful/blocks/search-block/index.tsx +90 -90
  177. package/src/contentful/blocks/search-block/types.ts +15 -15
  178. package/src/contentful/blocks/shape-background-wrapper/ShapeBackgroundWrapper.stories.tsx +26 -26
  179. package/src/contentful/blocks/shape-background-wrapper/index.tsx +124 -124
  180. package/src/contentful/blocks/shape-background-wrapper/types.ts +36 -36
  181. package/src/contentful/blocks/text/Text.stories.tsx +23 -23
  182. package/src/contentful/blocks/text/index.tsx +12 -12
  183. package/src/contentful/blocks/text/types.ts +1 -1
  184. package/src/contentful/index.ts +105 -105
  185. package/src/hooks/contentful/use-contentful-rich-text.tsx +309 -309
  186. package/src/hooks/contentful/use-processed-check-list.ts +63 -63
  187. package/src/hooks/use-body-scroll-lock.ts +34 -34
  188. package/src/hooks/use-carousel-swipe.ts +264 -264
  189. package/src/hooks/use-outside-click.ts +17 -17
  190. package/src/index.ts +107 -101
  191. package/src/next/index.ts +5 -5
  192. package/src/setupTests.ts +46 -46
  193. package/src/stories/DocsTemplate.tsx +24 -24
  194. package/src/styles/globals.css +343 -343
  195. package/src/types/global.d.ts +9 -9
  196. package/src/types/micro-components.ts +99 -99
  197. package/src/types/utm.ts +49 -49
  198. package/src/utils/contentful/to-document.ts +24 -24
  199. package/src/utils/cookie.ts +84 -84
  200. package/src/utils/cx.ts +49 -49
  201. package/src/utils/index.ts +41 -41
  202. package/src/utils/speed-card-bg.ts +24 -24
  203. package/src/utils/utm.ts +221 -221
@@ -1,265 +1,279 @@
1
- import React from "react";
2
- import { Button } from "../button";
3
- import BlogCard from "../cards/blog-card";
4
- import FloatingImageCard from "../cards/floating-image-card";
5
- import FullImageCard from "../cards/full-image-card";
6
- import SimpleCard from "../cards/simple-card";
7
- import { CalloutCardType, CalloutItem, CalloutProps } from "./types";
8
-
9
- import { Text } from "@shared/components/text";
10
- import { cx } from "@shared/utils";
11
-
12
- const backgroundClassMap: Record<string, string> = {
13
- cream500: "bg-[#FFFEEF]",
14
- gray100: "bg-fill-secondary",
15
- white: "bg-white",
16
- transparent: "",
17
- blue: "bg-fill-brand",
18
- green: "bg-fill-brand-accent",
19
- navy: "bg-fill-inverse",
20
- purple: "bg-fill-brand-tertiary",
21
- yellow: "bg-[#F5FF1E]",
22
- };
23
-
24
- // Literal class strings (Tailwind JIT only picks up literal tokens; do not
25
- // build these by concatenation at runtime).
26
- // Per-card responsive width classes for the flex-wrap layout. Mobile is
27
- // always full-width; md renders 1 or 2 columns; lg renders up to 6.
28
- // The calc() values subtract a portion of the gap so cards fit cleanly.
29
- const mdWidthMap: Record<number, string> = {
30
- 1: "md:w-full",
31
- 2: "md:w-[calc(50%-1rem)]",
32
- };
33
- const lgWidthMap: Record<number, string> = {
34
- 1: "lg:w-full",
35
- 2: "lg:w-[calc(50%-0.75rem)]",
36
- 3: "lg:w-[calc(33.333%-1rem)]",
37
- 4: "lg:w-[calc(25%-1.125rem)]",
38
- 5: "lg:w-[calc(20%-1.2rem)]",
39
- 6: "lg:w-[calc(16.666%-1.25rem)]",
40
- };
41
-
42
- /**
43
- * Mirrors the local @ui Callout `calculateOptimalColumns` logic:
44
- * - ≤4 cards: one per column
45
- * - divisible by 3: 3 cols
46
- * - divisible by 4: 4 cols
47
- * - >6 cards: 4 cols
48
- * - else: 3 cols
49
- */
50
- const calculateOptimalColumns = (count: number): number => {
51
- if (count <= 4) return count || 1;
52
- if (count % 3 === 0) return 3;
53
- if (count % 4 === 0) return 4;
54
- if (count > 6) return 4;
55
- return 3;
56
- };
57
-
58
- const clampCol = (n: number) => Math.max(1, Math.min(6, n));
59
-
60
- export const Callout: React.FC<CalloutProps> = ({
61
- anchorId,
62
- title,
63
- items,
64
- enableHeading = false,
65
- subtitle,
66
- description,
67
- finePrint,
68
- cta,
69
- color = "dark",
70
- maxWidth = true,
71
- maxCardsPerRow,
72
- cardType = "simple",
73
- backgroundColor,
74
- background,
75
- textColor,
76
- containerClassName,
77
- innerClassName,
78
- applyBoxShadow = false,
79
- cardStackingMobile = true,
80
- cardsWidth = true,
81
- noGutter = false,
82
- }) => {
83
- const itemCount = items?.length ?? 0;
84
- const desktopCols = clampCol(
85
- maxCardsPerRow ?? calculateOptimalColumns(itemCount)
86
- );
87
- const lgCols = clampCol(Math.min(desktopCols, itemCount || desktopCols));
88
-
89
- // Layout selection:
90
- // - cardsWidth === false → explicit vertical stack (single column)
91
- // - any other value (true / undefined / string / etc.) → legacy
92
- // flex-wrap layout that always renders multi-column on md+ and
93
- // full-width on mobile. This matches the pre-0.1.79 DOM and keeps
94
- // consumers like SMB-browse rendering correctly without changes.
95
- const isStackMode = cardsWidth === false;
96
- const gridClass = isStackMode
97
- ? cx(
98
- "flex flex-col items-stretch self-stretch",
99
- noGutter ? "gap-0" : "gap-6"
100
- )
101
- : cx(
102
- "flex flex-wrap items-stretch justify-center self-stretch",
103
- noGutter ? "gap-0" : "gap-6",
104
- noGutter ? "md:gap-0" : "md:gap-6"
105
- );
106
-
107
- // Per-card width classes — only used for flex-wrap layout. Mobile is
108
- // full-width; md respects `cardStackingMobile` (1 col when true, 2 cols
109
- // when false); lg uses the optimal column count. Stack mode skips this.
110
- const cardWidthClass = isStackMode
111
- ? ""
112
- : cx(
113
- "w-full",
114
- mdWidthMap[clampCol(cardStackingMobile ? 1 : Math.min(2, desktopCols))],
115
- lgWidthMap[lgCols]
116
- );
117
-
118
- const renderCard = (item: CalloutItem, index: number) => {
119
- const itemCardType: CalloutCardType = item.cardType ?? cardType;
120
-
121
- // Stack mode preserves any legacy lgWidth/mdWidth on the card itself;
122
- // flex-wrap mode controls width via the wrapper div, so strip them.
123
- const widthProps = isStackMode
124
- ? { lgWidth: undefined, mdWidth: undefined }
125
- : {};
126
-
127
- switch (itemCardType) {
128
- case "blog": {
129
- const blogItem = item as any;
130
- return (
131
- <BlogCard
132
- key={index}
133
- title={blogItem.title}
134
- href={blogItem.slug}
135
- description={blogItem.shortDescription}
136
- date={blogItem.blogCreationDate}
137
- category={blogItem.category}
138
- image={blogItem.cover}
139
- asGrid={false}
140
- {...widthProps}
141
- />
142
- );
143
- }
144
- case "fullImage":
145
- return (
146
- <FullImageCard
147
- key={index}
148
- card={{
149
- ...(item as any),
150
- shadow: (item as any).shadow ?? applyBoxShadow,
151
- }}
152
- {...widthProps}
153
- />
154
- );
155
- case "floatingImage":
156
- return (
157
- <FloatingImageCard
158
- key={index}
159
- card={{
160
- ...(item as any),
161
- shadow: (item as any).shadow ?? applyBoxShadow,
162
- }}
163
- {...widthProps}
164
- />
165
- );
166
- case "simple":
167
- default:
168
- return (
169
- <SimpleCard
170
- key={index}
171
- card={{
172
- ...(item as any),
173
- shadow: (item as any).shadow ?? applyBoxShadow,
174
- }}
175
- {...widthProps}
176
- />
177
- );
178
- }
179
- };
180
-
181
- const sectionStyle = background ? { background } : undefined;
182
- const headingStyle = textColor ? { color: textColor } : undefined;
183
- const sectionBgClass = background
184
- ? ""
185
- : backgroundColor
186
- ? (backgroundClassMap[backgroundColor] ?? "")
187
- : "";
188
-
189
- return (
190
- <section
191
- id={anchorId}
192
- className={cx("component-container", sectionBgClass, containerClassName)}
193
- style={sectionStyle}
194
- >
195
- <div
196
- className={cx(
197
- noGutter ? "p-0" : "mx-5 my-12 md:my-20",
198
- maxWidth && "max-w-120 xl:mx-auto",
199
- color === "dark" ? "text-text" : "text-white",
200
- innerClassName
201
- )}
202
- >
203
- <div className="callout-container flex flex-col gap-10 md:gap-16">
204
- <div className="title-holder" style={headingStyle}>
205
- {title && (
206
- <Text
207
- as={enableHeading ? "h1" : "h2"}
208
- className="heading2 md:heading1 md:text-center"
209
- >
210
- {title}
211
- </Text>
212
- )}
213
- {subtitle && (
214
- <Text
215
- as={enableHeading ? "h2" : "h3"}
216
- className="subheading3 mt-3 md:subheading1 md:text-center"
217
- >
218
- {subtitle}
219
- </Text>
220
- )}
221
- {description && (
222
- <Text as="p" className="body1 mt-4 text-center md:mt-6">
223
- {description}
224
- </Text>
225
- )}
226
- </div>
227
- <div className={cx("card-holder", gridClass)}>
228
- {items.map((item, index: number) =>
229
- isStackMode ? (
230
- renderCard(item, index)
231
- ) : (
232
- <div
233
- key={`callout-card-${index}`}
234
- className={cx("callout-card", cardWidthClass)}
235
- >
236
- {renderCard(item, index)}
237
- </div>
238
- )
239
- )}
240
- </div>
241
- {(cta || finePrint) && (
242
- <div className="flex flex-col items-center gap-4">
243
- {cta ? (
244
- <Button
245
- linkClassName="label1"
246
- buttonClassName="label1"
247
- {...cta}
248
- >
249
- {cta.label ?? cta.buttonLabel}
250
- </Button>
251
- ) : null}
252
- {finePrint ? (
253
- <Text as="div" className="footnote text-center text-text">
254
- {finePrint}
255
- </Text>
256
- ) : null}
257
- </div>
258
- )}
259
- </div>
260
- </div>
261
- </section>
262
- );
263
- };
264
-
265
- export default Callout;
1
+ import React from "react";
2
+ import { Button } from "../button";
3
+ import BlogCard from "../cards/blog-card";
4
+ import FloatingImageCard from "../cards/floating-image-card";
5
+ import FullImageCard from "../cards/full-image-card";
6
+ import SimpleCard from "../cards/simple-card";
7
+ import { CalloutCardType, CalloutItem, CalloutProps } from "./types";
8
+
9
+ import { AnimationWrapper } from "@shared/components/animation-wrapper";
10
+ import { Text } from "@shared/components/text";
11
+ import { cx } from "@shared/utils";
12
+
13
+ const backgroundClassMap: Record<string, string> = {
14
+ cream500: "bg-[#FFFEEF]",
15
+ gray100: "bg-fill-secondary",
16
+ white: "bg-white",
17
+ transparent: "",
18
+ blue: "bg-fill-brand",
19
+ green: "bg-fill-brand-accent",
20
+ navy: "bg-fill-inverse",
21
+ purple: "bg-fill-brand-tertiary",
22
+ yellow: "bg-[#F5FF1E]",
23
+ };
24
+
25
+ // Literal class strings (Tailwind JIT only picks up literal tokens; do not
26
+ // build these by concatenation at runtime).
27
+ // Per-card responsive width classes for the flex-wrap layout. Mobile is
28
+ // always full-width; md renders 1 or 2 columns; lg renders up to 6.
29
+ // The calc() values subtract a portion of the gap so cards fit cleanly.
30
+ const mdWidthMap: Record<number, string> = {
31
+ 1: "md:w-full",
32
+ 2: "md:w-[calc(50%-1rem)]",
33
+ };
34
+ const lgWidthMap: Record<number, string> = {
35
+ 1: "lg:w-full",
36
+ 2: "lg:w-[calc(50%-0.75rem)]",
37
+ 3: "lg:w-[calc(33.333%-1rem)]",
38
+ 4: "lg:w-[calc(25%-1.125rem)]",
39
+ 5: "lg:w-[calc(20%-1.2rem)]",
40
+ 6: "lg:w-[calc(16.666%-1.25rem)]",
41
+ };
42
+
43
+ /**
44
+ * Mirrors the local @ui Callout `calculateOptimalColumns` logic:
45
+ * - ≤4 cards: one per column
46
+ * - divisible by 3: 3 cols
47
+ * - divisible by 4: 4 cols
48
+ * - >6 cards: 4 cols
49
+ * - else: 3 cols
50
+ */
51
+ const calculateOptimalColumns = (count: number): number => {
52
+ if (count <= 4) return count || 1;
53
+ if (count % 3 === 0) return 3;
54
+ if (count % 4 === 0) return 4;
55
+ if (count > 6) return 4;
56
+ return 3;
57
+ };
58
+
59
+ const clampCol = (n: number) => Math.max(1, Math.min(6, n));
60
+
61
+ export const Callout: React.FC<CalloutProps> = ({
62
+ anchorId,
63
+ title,
64
+ items,
65
+ enableHeading = false,
66
+ subtitle,
67
+ description,
68
+ finePrint,
69
+ cta,
70
+ color = "dark",
71
+ maxWidth = true,
72
+ maxCardsPerRow,
73
+ cardType = "simple",
74
+ backgroundColor,
75
+ background,
76
+ textColor,
77
+ containerClassName,
78
+ innerClassName,
79
+ applyBoxShadow = false,
80
+ cardStackingMobile = true,
81
+ cardsWidth = true,
82
+ noGutter = false,
83
+ disableAnimation = true,
84
+ animationType = "lift",
85
+ }) => {
86
+ const itemCount = items?.length ?? 0;
87
+ const desktopCols = clampCol(
88
+ maxCardsPerRow ?? calculateOptimalColumns(itemCount)
89
+ );
90
+ const lgCols = clampCol(Math.min(desktopCols, itemCount || desktopCols));
91
+
92
+ // Layout selection:
93
+ // - cardsWidth === false → explicit vertical stack (single column)
94
+ // - any other value (true / undefined / string / etc.) → legacy
95
+ // flex-wrap layout that always renders multi-column on md+ and
96
+ // full-width on mobile. This matches the pre-0.1.79 DOM and keeps
97
+ // consumers like SMB-browse rendering correctly without changes.
98
+ const isStackMode = cardsWidth === false;
99
+ const gridClass = isStackMode
100
+ ? cx(
101
+ "flex flex-col items-stretch self-stretch",
102
+ noGutter ? "gap-0" : "gap-6"
103
+ )
104
+ : cx(
105
+ "flex flex-wrap items-stretch justify-center self-stretch",
106
+ noGutter ? "gap-0" : "gap-6",
107
+ noGutter ? "md:gap-0" : "md:gap-6"
108
+ );
109
+
110
+ // Per-card width classes — only used for flex-wrap layout. Mobile is
111
+ // full-width; md respects `cardStackingMobile` (1 col when true, 2 cols
112
+ // when false); lg uses the optimal column count. Stack mode skips this.
113
+ const cardWidthClass = isStackMode
114
+ ? ""
115
+ : cx(
116
+ "w-full",
117
+ mdWidthMap[clampCol(cardStackingMobile ? 1 : Math.min(2, desktopCols))],
118
+ lgWidthMap[lgCols]
119
+ );
120
+
121
+ const renderCard = (item: CalloutItem, index: number) => {
122
+ const itemCardType: CalloutCardType = item.cardType ?? cardType;
123
+
124
+ // Stack mode preserves any legacy lgWidth/mdWidth on the card itself;
125
+ // flex-wrap mode controls width via the wrapper div, so strip them.
126
+ const widthProps = isStackMode
127
+ ? { lgWidth: undefined, mdWidth: undefined }
128
+ : {};
129
+
130
+ switch (itemCardType) {
131
+ case "blog": {
132
+ const blogItem = item as any;
133
+ return (
134
+ <BlogCard
135
+ key={index}
136
+ title={blogItem.title}
137
+ href={blogItem.slug}
138
+ description={blogItem.shortDescription}
139
+ date={blogItem.blogCreationDate}
140
+ category={blogItem.category}
141
+ image={blogItem.cover}
142
+ asGrid={false}
143
+ {...widthProps}
144
+ />
145
+ );
146
+ }
147
+ case "fullImage":
148
+ return (
149
+ <FullImageCard
150
+ key={index}
151
+ card={{
152
+ ...(item as any),
153
+ shadow: (item as any).shadow ?? applyBoxShadow,
154
+ }}
155
+ {...widthProps}
156
+ />
157
+ );
158
+ case "floatingImage":
159
+ return (
160
+ <FloatingImageCard
161
+ key={index}
162
+ card={{
163
+ ...(item as any),
164
+ shadow: (item as any).shadow ?? applyBoxShadow,
165
+ }}
166
+ {...widthProps}
167
+ />
168
+ );
169
+ case "simple":
170
+ default:
171
+ return (
172
+ <SimpleCard
173
+ key={index}
174
+ card={{
175
+ ...(item as any),
176
+ shadow: (item as any).shadow ?? applyBoxShadow,
177
+ }}
178
+ {...widthProps}
179
+ />
180
+ );
181
+ }
182
+ };
183
+
184
+ const sectionStyle = background ? { background } : undefined;
185
+ const headingStyle = textColor ? { color: textColor } : undefined;
186
+ const sectionBgClass = background
187
+ ? ""
188
+ : backgroundColor
189
+ ? (backgroundClassMap[backgroundColor] ?? "")
190
+ : "";
191
+
192
+ return (
193
+ <section
194
+ id={anchorId}
195
+ className={cx("component-container", sectionBgClass, containerClassName)}
196
+ style={sectionStyle}
197
+ >
198
+ <div
199
+ className={cx(
200
+ noGutter ? "p-0" : "mx-5 my-12 md:my-20",
201
+ maxWidth && "max-w-120 xl:mx-auto",
202
+ color === "dark" ? "text-text" : "text-white",
203
+ innerClassName
204
+ )}
205
+ >
206
+ <div className="callout-container flex flex-col gap-10 md:gap-16">
207
+ <div className="title-holder" style={headingStyle}>
208
+ {title && (
209
+ <Text
210
+ as={enableHeading ? "h1" : "h2"}
211
+ className="heading2 md:heading1 md:text-center"
212
+ >
213
+ {title}
214
+ </Text>
215
+ )}
216
+ {subtitle && (
217
+ <Text
218
+ as={enableHeading ? "h2" : "h3"}
219
+ className="subheading3 mt-3 md:subheading1 md:text-center"
220
+ >
221
+ {subtitle}
222
+ </Text>
223
+ )}
224
+ {description && (
225
+ <Text as="p" className="body1 mt-4 text-center md:mt-6">
226
+ {description}
227
+ </Text>
228
+ )}
229
+ </div>
230
+ <div className={cx("card-holder", gridClass)}>
231
+ {items.map((item, index: number) =>
232
+ isStackMode ? (
233
+ <AnimationWrapper
234
+ key={`callout-card-${index}`}
235
+ animationType={animationType}
236
+ disableAnimation={disableAnimation}
237
+ >
238
+ {renderCard(item, index)}
239
+ </AnimationWrapper>
240
+ ) : (
241
+ <div
242
+ key={`callout-card-${index}`}
243
+ className={cx("callout-card", cardWidthClass)}
244
+ >
245
+ <AnimationWrapper
246
+ animationType={animationType}
247
+ disableAnimation={disableAnimation}
248
+ >
249
+ {renderCard(item, index)}
250
+ </AnimationWrapper>
251
+ </div>
252
+ )
253
+ )}
254
+ </div>
255
+ {(cta || finePrint) && (
256
+ <div className="flex flex-col items-center gap-4">
257
+ {cta ? (
258
+ <Button
259
+ linkClassName="label1"
260
+ buttonClassName="label1"
261
+ {...cta}
262
+ >
263
+ {cta.label ?? cta.buttonLabel}
264
+ </Button>
265
+ ) : null}
266
+ {finePrint ? (
267
+ <Text as="div" className="footnote text-center text-text">
268
+ {finePrint}
269
+ </Text>
270
+ ) : null}
271
+ </div>
272
+ )}
273
+ </div>
274
+ </div>
275
+ </section>
276
+ );
277
+ };
278
+
279
+ export default Callout;